Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
JobManager.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
18#include "RooFit/MultiProcess/Queue.h" // complete type for JobManager::queue()
19#include "FIFOQueue.h" // complete type for JobManager::queue()
20#include "PriorityQueue.h" // complete type for JobManager::queue()
24
25namespace RooFit {
26namespace MultiProcess {
27
28/** \class JobManager
29 *
30 * \brief Main point of access for all MultiProcess infrastructure
31 *
32 * This class mainly serves as the access point to the multi-process infrastructure
33 * for 'Job's. It is meant to be used as a singleton that holds and connects the other
34 * infrastructural classes: the messenger, process manager, worker and queue loops.
35 *
36 * It is important that the user of this class, particularly the one that calls
37 * 'instance()' first, calls 'activate()' soon after, because everything that is
38 * done in between 'instance()' and 'activate()' will be executed on all processes.
39 * This may be useful in some cases, but in general, one will probably want to always
40 * use the 'JobManager' in its full capacity, including the queue and worker loops.
41 * This is the way the Job class uses this class, see 'Job::get_manager()'.
42 *
43 * The default number of processes is set using 'std::thread::hardware_concurrency()'.
44 * To change it, use 'Config::setDefaultNWorkers()' to set it to a different value
45 * before creation of a new JobManager instance.
46 */
47
48// static function
50{
52 instance_.reset(new JobManager(Config::getDefaultNWorkers())); // can't use make_unique, because ctor is private
53 instance_->messenger().test_connections(instance_->process_manager());
54 }
55 return instance_.get();
56}
57
58// static function
60{
61 return static_cast<bool>(instance_);
62}
63
64// (private) constructor
65/// Don't construct JobManager objects manually, use the static instance if
66/// you need to run multiple jobs.
67JobManager::JobManager(std::size_t N_workers)
68{
71 queue_ptr_ = std::make_unique<FIFOQueue>();
72 break;
73 }
75 queue_ptr_ = std::make_unique<PriorityQueue>();
76 break;
77 }
78 }
79 process_manager_ptr_ = std::make_unique<ProcessManager>(N_workers);
80 messenger_ptr_ = std::make_unique<Messenger>(*process_manager_ptr_);
81}
82
84{
85 // The instance typically gets created by some Job. Once all Jobs are gone, the
86 // JM will get destroyed. In this case, the job_objects map should have
87 // been emptied.
88 // The second case is when the program ends, at which time the static instance
89 // is destroyed. Jobs may still be present, for instance, the Job subclass
90 // RooFit::TestStatistics::LikelihoodGradientJob, will have
91 // been put into RooMinimizer::_theFitter->fObjFunction, as the gradient
92 // member. Because _theFitter is also a global static member, we cannot
93 // guarantee destruction order, and so the JobManager may be destroyed before
94 // all Jobs are destroyed. We cannot therefore make sure that the first
95 // condition is met. However, the Job objects stuck in _theFitter are not
96 // meant to be run again, because the program is ending anyway. So also in this
97 // case, we can safely shut down.
98 // There used to be an assert statement that checked whether the job_objects
99 // map was empty at destruction time, but that neglected the second possibility
100 // and led to assertion failures, which left the Messenger and ProcessManager
101 // objects intact, leading to the forked processes and their communication
102 // resources to remain after exiting the main/master/parent process.
103 // Note the destruction order: the ProcessManager first terminates the child
104 // processes (SIGTERM) while all communication channels are still open, so
105 // that no process sees a closed connection during a normal shutdown; only
106 // then the Messenger closes the channels.
107 process_manager_ptr_.reset();
108 messenger_ptr_.reset();
109 queue_ptr_.reset();
110}
111
112// static function
113/// \return job_id for added job_object
115{
117 if (instance_->process_manager().is_initialized()) {
118 std::stringstream ss;
119 ss << "Cannot add Job to JobManager instantiation, forking has already taken place! Instance object at raw "
120 "ptr "
121 << instance_.get();
122 throw std::logic_error("Cannot add Job to JobManager instantiation, forking has already taken place! Call "
123 "terminate() on the instance before adding new Jobs.");
124 }
125 }
126 std::size_t job_id = job_counter_++;
127 job_objects_[job_id] = job_object;
128 return job_id;
129}
130
131// static function
133{
134 auto found = job_objects_.find(job_object_id);
135 if (found == job_objects_.end()) {
136 throw std::runtime_error("JobManager::get_job_object: unknown job ID " + std::to_string(job_object_id) +
137 ", the interprocess message stream may be corrupted");
138 }
139 return found->second;
140}
141
142// static function
143/// \return Returns 'true' when removed successfully, 'false' otherwise.
145{
147 if (job_objects_.empty()) {
148 instance_.reset();
149 }
151}
152
157
159{
160 return *messenger_ptr_;
161}
162
164{
165 return queue_ptr_.get();
166}
167
168/// Retrieve results for a Job
169///
170/// \param requesting_job_id ID number of the Job in the JobManager's Job list
172{
173 if (process_manager().is_master()) {
174 bool job_fully_retrieved = false;
175 while (not job_fully_retrieved) {
176 try {
178 if (task_result_message.size() < sizeof(std::size_t)) {
179 throw std::runtime_error("JobManager::retrieve: received a task result message that is too short to "
180 "contain a job ID, the interprocess message stream may be corrupted");
181 }
182 auto job_object_id = *task_result_message.data<std::size_t>(); // job_id must always be the first element of
183 // the result message!
185 JobManager::get_job_object(job_object_id)->receive_task_result_on_master(task_result_message);
188 }
189 } catch (ppoll_error_t &) {
190 throw std::logic_error("in JobManager::retrieve: master received a SIGTERM, aborting");
191 }
192 }
193 }
194}
195
196/// \brief Start queue and worker loops on child processes
197///
198/// This function exists purely because activation from the constructor is
199/// impossible; the constructor must return a constructed instance, which it
200/// can't do if it's stuck in an infinite loop. This means the Job that first
201/// creates the JobManager instance must also activate it (or any other user
202/// of this class).
203/// This should be called soon after creation of instance, because everything
204/// between construction and activation gets executed both on the master
205/// process and on the slaves.
207{
208 activated_ = true;
209
210 // Note on error handling: the queue and worker processes are forked from
211 // the master, so the stack below this function belongs to the master-side
212 // caller. An exception escaping the event loops (e.g. from a closed
213 // connection when another process died unexpectedly) must therefore never
214 // propagate out of this function on a child process: it would unwind into
215 // code that was never meant to run on this process. Report it and exit.
216 if (process_manager().is_queue()) {
217 try {
218 queue()->loop();
219 } catch (const std::exception &e) {
220 fprintf(stderr, "queue process (PID %d) exits after exception: %s\n", getpid(), e.what());
221 std::_Exit(1);
222 }
223 std::_Exit(0);
224 }
225
226 if (!is_worker_loop_running() && process_manager().is_worker()) {
227 try {
229 } catch (const std::exception &e) {
230 fprintf(stderr, "worker process (PID %d) exits after exception: %s\n", getpid(), e.what());
231 std::_Exit(1);
232 }
233 std::_Exit(0);
234 }
235}
236
238{
239 return activated_;
240}
241
242// initialize static members
243std::map<std::size_t, Job *> JobManager::job_objects_;
244std::size_t JobManager::job_counter_ = 0;
245std::unique_ptr<JobManager> JobManager::instance_{nullptr};
246
247} // namespace MultiProcess
248} // 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.
const_iterator end() const
static unsigned int getDefaultNWorkers()
Definition Config.cxx:92
Main point of access for all MultiProcess infrastructure.
Definition JobManager.h:30
std::unique_ptr< Messenger > messenger_ptr_
Definition JobManager.h:54
std::unique_ptr< Queue > queue_ptr_
Definition JobManager.h:55
static std::size_t add_job_object(Job *job_object)
static JobManager * instance()
static std::size_t job_counter_
Definition JobManager.h:59
static Job * get_job_object(std::size_t job_object_id)
ProcessManager & process_manager() const
JobManager(std::size_t N_workers)
Don't construct JobManager objects manually, use the static instance if you need to run multiple jobs...
std::unique_ptr< ProcessManager > process_manager_ptr_
Definition JobManager.h:53
static std::map< std::size_t, Job * > job_objects_
Definition JobManager.h:58
static bool remove_job_object(std::size_t job_object_id)
static std::unique_ptr< JobManager > instance_
Definition JobManager.h:60
void retrieve(std::size_t requesting_job_id)
Retrieve results for a Job.
void activate()
Start queue and worker loops on child processes.
interface class for defining the actual work that must be done
Definition Job.h:26
A contiguous byte buffer used as the unit of interprocess communication.
Definition Message.h:30
Manages the interprocess communication channels and wraps send and receive calls.
value_t receive_from_worker_on_master(bool *more=nullptr)
Definition Messenger.h:216
Fork processes for queue and workers.
Keeps a queue of tasks for workers and manages the queue process through its event loop.
Definition Queue.h:22
void loop()
The queue process's event loop.
Definition Queue.cxx:85
Thrown when a blocking wait on a Channel is interrupted, e.g.
Definition Channel.h:31
void worker_loop()
The worker processes' event loop.
Definition worker.cxx:44
bool is_worker_loop_running()
Definition worker.cxx:35
The namespace RooFit contains mostly switches that change the behaviour of functions of PDFs (or othe...
Definition CodegenImpl.h:73
static QueueType getQueueType()
Definition Config.cxx:107