Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
ProcessManager.cxx
Go to the documentation of this file.
1/*
2 * Project: RooFit
3 * Authors:
4 * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl
5 * IP, Inti Pelupessy, Netherlands eScience Center, i.pelupessy@esciencecenter.nl
6 *
7 * Copyright (c) 2021, CERN
8 *
9 * Redistribution and use in source and binary forms,
10 * with or without modification, are permitted according to the terms
11 * listed in LICENSE (http://roofit.sourceforge.net/license.txt)
12 */
13
19
20#include <thread>
21#include <cstring> // for strsignal
22#include <fcntl.h> // for fcntl, O_NONBLOCK
23#include <sys/socket.h> // for socketpair
24#include <sys/wait.h> // for wait
25#include <iostream>
26#include <unordered_set>
27
28namespace RooFit {
29namespace MultiProcess {
30
31/// \class ProcessManager
32/// \brief Fork processes for queue and workers
33///
34/// This class manages three types of processes:
35/// 1. master: the initial main process. It defines and enqueues tasks
36/// and processes results.
37/// 2. workers: a pool of processes that will try to take tasks from the
38/// queue. These are forked from master.
39/// 3. queue: This process runs the queue_loop and maintains the queue of
40/// tasks. It is also forked from master.
41///
42/// \param N_workers Number of worker processes to spawn.
43ProcessManager::ProcessManager(std::size_t N_workers) : N_workers_(N_workers)
44{
45 // The socketpairs used for interprocess communication must be created
46 // before forking, so that all processes inherit the file descriptors of
47 // the connected channels.
50}
51
61
62// static member initialization
66
67// static function
68/// We need this to tell the children to die, because we can't talk
69/// to them anymore during JobManager destruction, because that kills
70/// the Messenger first. We do that with SIGTERMs. The sigterm_received()
71/// should be checked in message loops to stop them when it's true.
72/// The handler also writes to a self-pipe, so that a poll that is entered
73/// after the flag check but before signal delivery still wakes up.
75{
77 if (sigterm_wake_write_fd_ >= 0) {
78 char byte = 't';
79 // write is async-signal-safe; a full pipe just means a wake-up is already pending
80 ssize_t unused = write(sigterm_wake_write_fd_, &byte, 1);
81 (void)unused;
82 }
83}
84
85// static function
90
91// static function
93{
94 if (sigterm_received_ > 0) {
95 return true;
96 } else {
97 return false;
98 }
99}
100
102{
103 pid_t child_pid = fork();
104 int retries = 0;
105 while (child_pid == -1) {
106 if (retries < 3) {
107 ++retries;
108 printf("fork returned with error number %d, retrying after 1 second...\n", errno);
109 sleep(1);
110 child_pid = fork();
111 } else {
112 printf("fork returned with error number %d\n", errno);
113 throw std::runtime_error("fork returned with error 3 times, aborting!");
114 }
115 }
116 return child_pid;
117}
118
119namespace {
120
121/// Set FD_CLOEXEC so that the descriptor is not leaked into programs that a
122/// process executes (e.g. with gSystem->Exec). Leaked duplicates of the
123/// channel descriptors would keep the connections open after the owning
124/// process dies, defeating the closed-connection detection in Channel.
125void set_close_on_exec(int fd)
126{
127 int flags = fcntl(fd, F_GETFD, 0);
128 if (flags == -1 || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) {
129 throw std::runtime_error(std::string("ProcessManager: could not set FD_CLOEXEC: ") + strerror(errno));
130 }
131}
132
133void make_socketpair(std::array<int, 2> &fds)
134{
135 if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds.data()) != 0) {
136 throw std::runtime_error(std::string("ProcessManager: socketpair failed: ") + strerror(errno));
137 }
140}
141
142void close_fd_pair(std::array<int, 2> &fds, int keep = -1)
143{
144 for (int &fd : fds) {
145 if (fd >= 0 && fd != keep) {
146 close(fd);
147 fd = -1;
148 }
149 }
150}
151
152int claim_fd(int &fd)
153{
154 if (fd < 0) {
155 throw std::logic_error("ProcessManager: channel file descriptor already claimed or not owned by this process");
156 }
157 int result = fd;
158 fd = -1;
159 return result;
160}
161
162} // namespace
163
164/// Create the socketpairs that connect the processes. Must be called before
165/// forking; every process then keeps only the ends it needs (see
166/// close_unused_channel_fds).
168{
170 qw_fds_.resize(N_workers_, {{-1, -1}});
171 mw_fds_.resize(N_workers_, {{-1, -1}});
172 for (std::size_t ix = 0; ix < N_workers_; ++ix) {
175 }
176}
177
178/// Close the channel ends that do not belong to the current process type.
180{
181 for (std::size_t ix = 0; ix < N_workers_; ++ix) {
182 if (is_master_) {
184 close_fd_pair(mw_fds_[ix], mw_fds_[ix][0]);
185 } else if (is_queue_) {
186 close_fd_pair(qw_fds_[ix], qw_fds_[ix][0]);
188 } else { // worker
189 close_fd_pair(qw_fds_[ix], ix == worker_id_ ? qw_fds_[ix][1] : -1);
190 close_fd_pair(mw_fds_[ix], ix == worker_id_ ? mw_fds_[ix][1] : -1);
191 }
192 }
193 if (is_master_) {
195 } else if (is_queue_) {
197 } else {
199 }
200}
201
202/// Close all channel ends still owned by this ProcessManager (i.e. not
203/// claimed by a Messenger).
205{
207 for (auto &fds : qw_fds_) {
209 }
210 for (auto &fds : mw_fds_) {
212 }
213}
214
215/// Hand over the master-queue channel end for the current process type.
217{
218 return claim_fd(is_master_ ? mq_fds_[0] : mq_fds_[1]);
219}
220
221/// Hand over the queue-worker channel end for the current process type.
223{
225}
226
227/// Hand over the master-worker channel end for the current process type.
229{
231}
232
233/// \brief Fork processes and activate CPU pinning
234///
235/// \param cpu_pinning Activate CPU pinning if true. Effective on Linux only.
237{
238 // Initialize processes;
239 // ... first workers:
240
241 // Setup process timer master and assign pid_t 999
243
244 worker_pids_.resize(N_workers_);
245 pid_t child_pid{};
246 for (std::size_t ix = 0; ix < N_workers_; ++ix) {
248 if (!child_pid) { // we're on the worker
249 // Setup process timer, do not overwrite begin time, this keeps timing
250 // synced between worker and master processes. The forked process keeps
251 // the master process' begin time
253 is_worker_ = true;
254 worker_id_ = ix;
255 break;
256 } else { // we're on master
258 }
259 }
260
261 // ... then queue:
262 if (child_pid) { // we're on master
264 if (!queue_pid_) { // we're now on queue
265 is_queue_ = true;
266 } else {
267 is_master_ = true;
268 }
269 }
270
272
273 // set the sigterm handler on the child processes
274 if (!is_master_) {
275 // Create the self-pipe that the handler writes to before installing the
276 // handler. The pipe wakes up any poll on the channels, also when the
277 // signal arrived just before the poll was entered (see Channel::wait).
278 if (sigterm_wake_read_fd_ < 0) {
279 int pipe_fds[2];
280 if (pipe(pipe_fds) != 0) {
281 std::perror("pipe failed");
282 std::exit(1);
283 }
284 for (int fd : pipe_fds) {
285 int flags = fcntl(fd, F_GETFL, 0);
286 if (flags == -1 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
287 std::perror("fcntl failed");
288 std::exit(1);
289 }
290 int fd_flags = fcntl(fd, F_GETFD, 0);
291 if (fd_flags == -1 || fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC) == -1) {
292 std::perror("fcntl failed");
293 std::exit(1);
294 }
295 }
298 }
299
300 struct sigaction sa;
301 memset(&sa, '\0', sizeof(sa));
303
304 if (sigaction(SIGTERM, &sa, nullptr) < 0) {
305 std::perror("sigaction failed");
306 std::exit(1);
307 }
308 }
309
310 if (cpu_pinning) {
311#if defined(__APPLE__)
312#ifndef NDEBUG
313 static bool affinity_warned = false;
314 if (is_master() & !affinity_warned) {
315 std::cout << "CPU affinity cannot be set on macOS" << std::endl;
316 affinity_warned = true;
317 }
318#endif // NDEBUG
319#elif defined(_WIN32)
320#ifndef NDEBUG
321 if (is_master())
322 std::cerr << "WARNING: CPU affinity setting not implemented on Windows, continuing..." << std::endl;
323#endif // NDEBUG
324#else
326 // zero all bits in mask
327 CPU_ZERO(&mask);
328 // set correct bit
329 std::size_t set_cpu;
330 if (is_master()) {
331 set_cpu = N_workers() + 1;
332 } else if (is_queue()) {
333 set_cpu = N_workers();
334 } else {
335 set_cpu = worker_id();
336 }
338#ifndef NDEBUG
339 // sched_setaffinity returns 0 on success
340 if (sched_setaffinity(0, sizeof(mask), &mask) == -1) {
341 std::cerr << "WARNING: Could not set CPU affinity, continuing..." << std::endl;
342 } else {
343 std::cerr << "CPU affinity set to cpu " << set_cpu << " in process " << getpid() << std::endl;
344 }
345#endif // NDEBUG
346#endif
347 }
348
349#ifndef NDEBUG
351#endif // NDEBUG
352
353 initialized_ = true;
354}
355
357{
358 return initialized_;
359}
360
361/// Shutdown forked processes if on master and if this process manager is initialized
363{
364 try {
365 if (is_master() && is_initialized()) {
367 }
368 } catch (const std::exception &e) {
369 std::cerr << "WARNING: something in ProcessManager::terminate threw an exception! Original exception message:\n"
370 << e.what() << std::endl;
371 }
372}
373
375{
376 if (!is_master()) {
377 while (!sigterm_received()) {
378 }
379 std::_Exit(0);
380 }
381}
382
384{
385 int status = 0;
386 pid_t pid;
387 do {
388 pid = wait(&status);
389 } while (-1 == pid && EINTR == errno); // retry on interrupted system call
390
391 if (0 != status) {
392 if (WIFEXITED(status)) {
393 printf("exited, status=%d\n", WEXITSTATUS(status));
394 } else if (WIFSIGNALED(status)) {
395 if (WTERMSIG(status) != SIGTERM) {
396 printf("killed by signal %d\n", WTERMSIG(status));
397 }
398 } else if (WIFSTOPPED(status)) {
399 printf("stopped by signal %d\n", WSTOPSIG(status));
400 } else if (WIFCONTINUED(status)) {
401 printf("continued\n");
402 }
403 }
404
405 if (-1 == pid) {
406 if (errno == ECHILD) {
407 printf("chill_wait: no children (got ECHILD error code from wait call), done\n");
408 } else {
409 throw std::runtime_error(std::string("chill_wait: error in wait call: ") + strerror(errno) +
410 std::string(", errno ") + std::to_string(errno));
411 }
412 }
413
414 return pid;
415}
416
417/// Shutdown forked processes if on master
419{
420 if (is_master()) {
422 // Give children some time to write to file
423 if (RooFit::MultiProcess::Config::getTimingAnalysis()) std::this_thread::sleep_for(std::chrono::seconds(2));
424 // terminate all children
425 std::unordered_set<pid_t> children;
426 children.insert(queue_pid_);
428 for (auto pid : worker_pids_) {
429 kill(pid, SIGTERM);
430 children.insert(pid);
431 }
432 // then wait for them to actually die and clean out the zombies
433 while (!children.empty()) {
434 pid_t pid = chill_wait();
435 children.erase(pid);
436 }
437 }
438
439 initialized_ = false;
440}
441
442// Getters
443
445{
446 return is_master_;
447}
448
450{
451 return is_queue_;
452}
453
455{
456 return is_worker_;
457}
458
459std::size_t ProcessManager::worker_id() const
460{
461 return worker_id_;
462}
463
464std::size_t ProcessManager::N_workers() const
465{
466 return N_workers_;
467}
468
469/// Print to stdout which type of process we are on and what its PID is (for debugging)
471{
472 if (is_worker_) {
473 printf("I'm a worker, PID %d\n", getpid());
474 } else if (is_master_) {
475 printf("I'm master, PID %d\n", getpid());
476 } else if (is_queue_) {
477 printf("I'm queue, PID %d\n", getpid());
478 } else {
479 printf("I'm not master, queue or worker, weird! PID %d\n", getpid());
480 }
481}
482
483} // namespace MultiProcess
484} // namespace RooFit
#define e(i)
Definition RSha256.hxx:103
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t mask
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t result
R__EXTERN C unsigned int sleep(unsigned int seconds)
static bool getTimingAnalysis()
Definition Config.cxx:87
void initialize_processes(bool cpu_pinning=true)
Fork processes and activate CPU pinning.
std::vector< std::array< int, 2 > > qw_fds_
void close_channel_fds()
Close all channel ends still owned by this ProcessManager (i.e.
static void handle_sigterm(int signum)
We need this to tell the children to die, because we can't talk to them anymore during JobManager des...
int claim_mw_fd(std::size_t worker_ix)
Hand over the master-worker channel end for the current process type.
void close_unused_channel_fds()
Close the channel ends that do not belong to the current process type.
int claim_mq_fd()
Hand over the master-queue channel end for the current process type.
std::vector< std::array< int, 2 > > mw_fds_
void identify_processes() const
Print to stdout which type of process we are on and what its PID is (for debugging)
static int sigterm_wake_fd()
Read end of the self-pipe that the SIGTERM handler writes to (or -1 on the master process,...
void terminate() noexcept
Shutdown forked processes if on master and if this process manager is initialized.
void shutdown_processes()
Shutdown forked processes if on master.
static volatile sig_atomic_t sigterm_received_
int claim_qw_fd(std::size_t worker_ix)
Hand over the queue-worker channel end for the current process type.
void create_channel_fds()
Create the socketpairs that connect the processes.
static void setup(pid_t proc, bool set_begin=true)
The namespace RooFit contains mostly switches that change the behaviour of functions of PDFs (or othe...
Definition CodegenImpl.h:73