Project
Loading...
Searching...
No Matches
o2sim_parallel.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
13
15#include <fairmq/TransportFactory.h>
16#include <fairmq/Channel.h>
17#include <fairmq/Message.h>
18
19#include <cstdlib>
20#include <unistd.h>
21#include <ctime>
22#include <sstream>
23#include <iostream>
24#include <cstdio>
25#include <fcntl.h>
26#include <SimConfig/SimConfig.h>
27#include <sys/wait.h>
28#include <vector>
29#include <functional>
30#include <thread>
31#include <csignal>
32#include "TStopwatch.h"
33#include <fairlogger/Logger.h>
35#include "TFile.h"
36#include "TTree.h"
37#include <sys/types.h>
40#include "O2Version.h"
41#include <cstdio>
42#include <unordered_map>
43#include <filesystem>
44#include <atomic>
46#include "Headers/Stack.h"
47
51
52std::string getServerLogName()
53{
54 auto& conf = o2::conf::SimConfig::Instance();
55 std::stringstream str;
56 str << conf.getOutPrefix() << "_serverlog";
57 return str.str();
58}
59
60std::string getWorkerLogName()
61{
62 auto& conf = o2::conf::SimConfig::Instance();
63 std::stringstream str;
64 str << conf.getOutPrefix() << "_workerlog";
65 return str.str();
66}
67
68std::string getMergerLogName()
69{
70 auto& conf = o2::conf::SimConfig::Instance();
71 std::stringstream str;
72 str << conf.getOutPrefix() << "_mergerlog";
73 return str.str();
74}
75
77{
78 // remove all (known) socket files in /tmp
79 // using the naming convention /tmp/o2sim-.*PID
80 std::stringstream searchstr;
81 searchstr << "o2sim-.*-" << getpid() << "$";
82 auto filenames = o2::utils::listFiles("/tmp/", searchstr.str());
83 // remove those files
84 for (auto& fn : filenames) {
85 try {
86 std::filesystem::remove(std::filesystem::path(fn));
87 } catch (...) {
88 LOG(warn) << "Couldn't remove tmp file " << fn;
89 }
90 }
91}
92
93void cleanup()
94{
95 auto& conf = o2::conf::SimConfig::Instance();
96 if (conf.forwardKine()) {
97 auto factory = fair::mq::TransportFactory::CreateTransportFactory("zeromq");
98 auto forwardchannel = fair::mq::Channel{"kineforward", "pair", factory};
99 auto address = std::string{"ipc:///tmp/o2sim-hitmerger-kineforward-"} + std::to_string(getpid());
100 forwardchannel.Bind(address.c_str());
101 forwardchannel.Validate();
102 fair::mq::Parts parts;
103 fair::mq::MessagePtr payload(forwardchannel.NewMessage());
106 auto channelAlloc = o2::pmr::getTransportAllocator(forwardchannel.Transport());
107 auto header = o2::pmr::getMessage(o2::header::Stack{channelAlloc, sih});
108 parts.AddPart(std::move(header));
109 parts.AddPart(std::move(payload));
110 int timeoutinMS = 1000; // block for 1s max (other side might have disconnected already)
111 if (forwardchannel.Send(parts, timeoutinMS) > 0) {
112 LOGP(info, "SENDING END-OF-STREAM TO PROXY AT {}", address.c_str());
113 } else {
114 LOGP(warn, "SENDING END-OF-STREAM TIMED OUT; PEER PROBABLY NO LONGER CONNECTED");
115 }
116 }
119
120 // special mode in which we dump the output from various
121 // log files to terminal (mainly interesting for CI mode)
122 if (getenv("ALICE_O2SIM_DUMPLOG")) {
123 std::cerr << "------------- START OF EVENTSERVER LOG ----------" << std::endl;
124 std::stringstream catcommand1;
125 catcommand1 << "cat " << getServerLogName() << ";";
126 if (system(catcommand1.str().c_str()) != 0) {
127 LOG(warn) << "error executing system call";
128 }
129
130 std::cerr << "------------- START OF SIM WORKER(S) LOG --------" << std::endl;
131 std::stringstream catcommand2;
132 catcommand2 << "cat " << getWorkerLogName() << "*;";
133 if (system(catcommand2.str().c_str()) != 0) {
134 LOG(warn) << "error executing system call";
135 }
136
137 std::cerr << "------------- START OF MERGER LOG ---------------" << std::endl;
138 std::stringstream catcommand3;
139 catcommand3 << "cat " << getMergerLogName() << ";";
140 if (system(catcommand3.str().c_str()) != 0) {
141 LOG(warn) << "error executing system call";
142 }
143 }
144}
145
146// quick cross check of simulation output
148{
149 int errors = 0;
150 // We can put more or less complex things
151 // here.
152 auto& conf = o2::conf::SimConfig::Instance();
153 if (!conf.writeToDisc()) {
154 return 0;
155 }
156 // easy check: see if we have number of entries in output tree == number of events asked
157 std::string filename = o2::base::NameConf::getMCKinematicsFileName(conf.getOutPrefix().c_str());
158 TFile f(filename.c_str(), "OPEN");
159 if (f.IsZombie()) {
160 LOG(warn) << "Kinematics file corrupted or does not exist";
161 return 1;
162 }
163 auto tr = static_cast<TTree*>(f.Get("o2sim"));
164 if (!tr) {
165 errors++;
166 } else {
167 if (!conf.isFilterOutNoHitEvents()) {
168 if (tr->GetEntries() != conf.getNEvents()) {
169 LOG(warn) << "There are fewer events in the output than asked";
170 }
171 }
172 }
173 // add more simple checks
174
175 return errors;
176}
177
178// ---> THE FOLLOWING CAN BE PUT INTO A "STATE" STRUCT
179std::vector<int> gChildProcesses; // global vector of child pids
180// record distributed events in a container
181std::vector<int> gDistributedEvents;
182// record finished events in a container
183std::vector<int> gFinishedEvents;
185std::atomic<bool> gPrimServerIsInitialized = false;
186
187std::string getControlAddress()
188{
189 std::stringstream controlsocketname;
190 controlsocketname << "ipc:///tmp/o2sim-control-" << getpid();
191 return controlsocketname.str();
192}
194{
195 // creates names for an internal-only socket
196 // hashing to distinguish from more "public" sockets
197 std::hash<std::string> hasher;
198 std::stringstream str;
199 str << "o2sim-internal_" << getpid();
200 std::string tmp(std::to_string(hasher(str.str())));
201 std::stringstream controlsocketname;
202 controlsocketname << "ipc:///tmp/" << tmp.substr(0, 10) << "_" << getpid();
203 return controlsocketname.str();
204}
205
206bool isBusy()
207{
208 if (gFinishedEvents.size() != gAskedEvents) {
209 return true;
210 }
211 return false;
212}
213
214// launches a thread that listens for control command from outside
215// or that propagates control strings to all children
217{
218 static std::vector<std::thread> threads;
219 auto controladdress = getControlAddress();
220 auto internalcontroladdress = getInternalControlAddress();
221 LOG(info) << "Control address is: " << controladdress;
222 setenv("ALICE_O2SIMCONTROL", internalcontroladdress.c_str(), 1);
223
224 auto lambda = [controladdress, internalcontroladdress]() {
225 auto factory = fair::mq::TransportFactory::CreateTransportFactory("zeromq");
226
227 // used for internal distribution of control commands
228 auto internalchannel = fair::mq::Channel{"o2sim-internal", "pub", factory};
229 internalchannel.Bind(internalcontroladdress);
230 internalchannel.Validate();
231 std::unique_ptr<fair::mq::Message> message(internalchannel.NewMessage());
232
233 // the channel with which outside entities can control this simulator
234 auto outsidechannel = fair::mq::Channel{"o2sim-control", "rep", factory};
235 outsidechannel.Bind(controladdress);
236 outsidechannel.Validate();
237 std::unique_ptr<fair::mq::Message> request(outsidechannel.NewMessage());
238
239 bool keepgoing = true;
240 while (keepgoing) {
241 outsidechannel.Init();
242 outsidechannel.Bind(controladdress);
243 outsidechannel.Validate();
244 if (outsidechannel.Receive(request) > 0) {
245 std::string command(reinterpret_cast<char const*>(request->GetData()), request->GetSize());
246 LOG(info) << "Control message: " << command << " received ";
247 int code = -1;
248 if (isBusy()) {
249 code = 1; // code = 1 --> busy
250 std::unique_ptr<fair::mq::Message> reply(outsidechannel.NewSimpleMessage(code));
251 outsidechannel.Send(reply);
252 } else {
253 code = 0; // code = 0 --> ok
254
256 auto success = o2::conf::parseSimReconfigFromString(command, reconfig);
257 if (!success) {
258 LOG(warn) << "CONTROL REQUEST COULD NOT BE PARSED";
259 code = 2; // code = 2 --> error with request data
260 }
261 std::unique_ptr<fair::mq::Message> reply(outsidechannel.NewSimpleMessage(code));
262 outsidechannel.Send(reply);
263
264 if (code == 0) {
265 gAskedEvents = reconfig.nEvents;
266 gDistributedEvents.clear();
267 gFinishedEvents.clear();
268 // forward request from outside to all internal processes
269 internalchannel.Send(request);
270 keepgoing = !reconfig.stop;
271 }
272 }
273 }
274 }
275 };
276 threads.push_back(std::thread(lambda));
277 threads.back().detach();
278}
279
280// launches a thread that listens for control command from outside
281// or that propagates control strings to all children
283{
284 static std::vector<std::thread> threads;
285 auto lambda = []() {
286 auto factory = fair::mq::TransportFactory::CreateTransportFactory("zeromq");
287
288 auto listenchannel = fair::mq::Channel{"channel0", "sub", factory};
289 listenchannel.Init();
290 std::stringstream address;
291 address << "ipc:///tmp/o2sim-worker-notifications-" << getpid();
292 listenchannel.Connect(address.str());
293 listenchannel.Validate();
294 std::unique_ptr<fair::mq::Message> message(listenchannel.NewMessage());
295
296 while (true) {
297 if (listenchannel.Receive(message) > 0) {
298 std::string msg(reinterpret_cast<char const*>(message->GetData()), message->GetSize());
299 LOG(info) << "Worker message: " << msg;
300 }
301 }
302 };
303 threads.push_back(std::thread(lambda));
304 threads.back().detach();
305}
306
307// monitors a certain incoming event pipes and displays new information
308// gives possibility to exec a callback at these events
310 int pipefd, std::string text, std::vector<int>& eventcontainer,
311 std::function<bool(std::vector<int> const&)> callback = [](std::vector<int> const&) { return true; })
312{
313 static std::vector<std::thread> threads;
314 auto lambda = [pipefd, text, callback, &eventcontainer]() {
315 int eventcounter; // event id or some other int message
316 while (1) {
317 ssize_t count = read(pipefd, &eventcounter, sizeof(eventcounter));
318 if (count == -1) {
319 LOG(info) << "ERROR READING";
320 if (errno == EINTR) {
321 continue;
322 } else {
323 return;
324 }
325 } else if (count == 0) {
326 break;
327 } else {
328 eventcontainer.push_back(eventcounter);
329 if (callback(eventcontainer)) {
330 LOG(info) << text.c_str() << eventcounter;
331 }
332 }
333 };
334 };
335 threads.push_back(std::thread(lambda));
336 threads.back().detach();
337}
338
340{
341 static std::vector<std::thread> threads;
342 auto lambda = []() {
343 // once started ... we are waiting for some seconds
344 // then **force** shutdown all remaining children by killing them.
345 // This is to make sure that the process does not hang during a final wait
346 // and interrupted/blocked signal delivery.
347
348 struct timespec initial, remaining;
349 initial.tv_sec = 5;
350 // wait for specified time ... (and account for possible signal interruptions)
351 while (nanosleep(&initial, &remaining) == -1 && remaining.tv_sec > 0) {
352 initial = remaining;
353 }
354 LOG(info) << "Shutdown timer expired ... force killing remaining children";
355 for (auto p : gChildProcesses) {
356 if (p != 0 && killpg(p, 0) == 0) { // see if process still exists
357 killpg(p, SIGKILL);
358 }
359 }
360 };
361 threads.push_back(std::thread(lambda));
362 threads.back().detach();
363}
364
365void empty(int) {}
366
367// signal handler for graceful exit
368void sighandler(int sig)
369{
370 if (sig == SIGINT || sig == SIGTERM) {
371 signal(sig, empty); // ignore further deliveries of these signals
372 LOG(info) << "o2-sim driver: Signal caught ... clean up and exit (please be patient)";
373 // forward signal to all children
374 for (auto& pid : gChildProcesses) {
375 killpg(pid, sig);
376 }
377 cleanup();
378
379 // make sure everyone is really shutting down
380 int status, cpid;
382 while ((cpid = wait(&status))) {
383 if (cpid == -1) {
384 break;
385 }
386 }
387
388 exit(1); // exiting upon external signal is abnormal so exit code != 0
389 }
390}
391
392// We do some early checks on the arguments passed. In particular we fix
393// missing timestamps for consistent application in all sub-processes. An empty
394// vector is returned upon errors.
395std::vector<char*> checkArgs(int argc, char* argv[])
396{
397 auto conf = o2::conf::SimConfig::make();
398 std::vector<std::string> modifiedArgs;
399#ifdef SIM_RUN5
400 conf.setRun5();
401#endif
402 if (conf.resetFromArguments(argc, argv)) {
403 for (int i = 0; i < argc; ++i) {
404 modifiedArgs.push_back(argv[i]);
405 }
406
407 // Check the run and the time arguments and enforce consistency.
408 // This is important as queries to CCDB are done using the timestamp.
409 if (conf.getRunNumber() != -1) {
410 // if we have a run number we should fix or check the timestamp
411
412 // fetch the actual timestamp ranges for this run
413 auto& ccdbmgr = o2::ccdb::BasicCCDBManager::instance();
414 auto soreor = ccdbmgr.getRunDuration(conf.getRunNumber());
415 auto timestamp = conf.getTimestamp();
416 if (conf.getConfigData().mTimestampMode == o2::conf::TimeStampMode::kNow) {
417 timestamp = soreor.first;
418 LOG(info) << "Fixing timestamp to " << timestamp << " based on run number";
419 modifiedArgs.push_back("--timestamp");
420 modifiedArgs.push_back(std::to_string(timestamp));
421 } else if (conf.getConfigData().mTimestampMode == o2::conf::TimeStampMode::kManual && (timestamp < soreor.first || timestamp > soreor.second)) {
422 LOG(fatal) << "The given timestamp " << timestamp << " is incompatible with the given run number " << conf.getRunNumber() << " starting at " << soreor.first << " and ending at " << soreor.second;
423 }
424 }
425 }
426 std::vector<char*> final(modifiedArgs.size(), nullptr);
427 for (int i = 0; i < modifiedArgs.size(); ++i) {
428 final[i] = new char[modifiedArgs[i].size() + 1];
429 strcpy(final[i], modifiedArgs[i].c_str());
430 }
431 return final;
432}
433
434// helper executable to launch all the devices/processes
435// for parallel simulation
436int main(int argc, char* argv[])
437{
438 LOG(info) << "This is o2-sim version " << o2::fullVersion() << " (" << o2::gitRevision() << ")";
439 LOG(info) << o2::getBuildInfo();
440
441 signal(SIGINT, sighandler);
442 signal(SIGTERM, sighandler);
443 // we enable the forked version of the code by default
444 setenv("ALICE_SIMFORKINTERNAL", "ON", 1);
445
446 // force execution as own process group
447 if (setpgid(0, 0) == -1) {
448 perror("setpgid");
449 exit(1);
450 }
451
452 TStopwatch timer;
453 timer.Start();
454 auto o2env = getenv("O2_ROOT");
455 if (!o2env) {
456 LOG(fatal) << "O2_ROOT environment not defined";
457 }
458 std::string rootpath(o2env);
459 std::string installpath = rootpath + "/bin";
460
461 // copy topology file to working dir and update ports
462 std::stringstream configss;
463 configss << rootpath << "/share/config/o2simtopology_template.json";
464 auto localconfig = std::string("o2simtopology_") + std::to_string(getpid()) + std::string(".json");
465
466 // need to add pid to channel urls to allow simultaneous deploys!
467 // we simply insert the PID into the topology template
468 std::ifstream in(configss.str());
469 std::ofstream out(localconfig);
470 std::string wordToReplace("#PID#");
471 std::string wordToReplaceWith = std::to_string(getpid());
472 std::string line;
473 size_t len = wordToReplace.length();
474 while (std::getline(in, line)) {
475 size_t pos = line.find(wordToReplace);
476 if (pos != std::string::npos) {
477 line.replace(pos, len, wordToReplaceWith);
478 }
479 out << line << '\n';
480 }
481 in.close();
482 out.close();
483
484 // create a channel for outside event notifications --> factor out into common function
485 // auto factory = fair::mq::TransportFactory::CreateTransportFactory("zeromq");
486 auto externalpublishchannel = o2::simpubsub::createPUBChannel(o2::simpubsub::getPublishAddress("o2sim-notifications"));
487
488 // check initial arguments and complete
489 auto finalArgs = checkArgs(argc, argv);
490 if (finalArgs.size() == 0) {
491 return 1;
492 }
493
494 auto& conf = o2::conf::SimConfig::Instance();
495#ifdef SIM_RUN5
496 conf.setRun5();
497#endif
498 if (!conf.resetFromArguments(finalArgs.size(), &finalArgs[0])) {
499 return 1;
500 }
501 // in case of zero events asked (only setup geometry etc) we just call the non-distributed version
502 // (otherwise we would need to add more synchronization between the actors)
503 if (conf.getNEvents() <= 0 && !conf.asService()) {
504 LOG(info) << "No events to be simulated; Switching to non-distributed mode";
505 const int Nargs = finalArgs.size() + 1;
506#ifdef SIM_RUN5
507 std::string name("o2-sim-serial-run5");
508#else
509 std::string name("o2-sim-serial");
510#endif
511 const char* arguments[Nargs];
512 arguments[0] = name.c_str();
513 for (int i = 1; i < finalArgs.size(); ++i) {
514 arguments[i] = finalArgs[i];
515 }
516 arguments[finalArgs.size()] = nullptr;
517 std::string path = installpath + "/" + name;
518 auto r = execv(path.c_str(), (char* const*)arguments);
519 if (r != 0) {
520 perror(nullptr);
521 }
522 return r;
523 }
524
525 gAskedEvents = conf.getNEvents();
526 if (conf.asService()) {
528 // launchWorkerListenerThread();
529 }
530
531 // we create the global shared mem pool; just enough to serve
532 // n simulation workers
533 int nworkers = conf.getNSimWorkers();
534 setenv("ALICE_NSIMWORKERS", std::to_string(nworkers).c_str(), 1);
535 LOG(info) << "Running with " << nworkers << " sim workers ";
536
538
539 // we can try to disable it here
540 if (getenv("ALICE_NOSIMSHM")) {
542 }
543
544 int pipe_serverdriver_fd[2];
545 if (pipe(pipe_serverdriver_fd) != 0) {
546 perror("problem in creating pipe");
547 }
548
549 // the server
550 int pid = fork();
551 if (pid == 0) {
552 int fd = open(getServerLogName().c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
553 setenv("ALICE_O2SIMSERVERTODRIVER_PIPE", std::to_string(pipe_serverdriver_fd[1]).c_str(), 1);
554
555 dup2(fd, 1); // make stdout go to file
556 dup2(fd, 2); // make stderr go to file - you may choose to not do this
557 // or perhaps send stderr to another file
558 close(pipe_serverdriver_fd[0]);
559 close(fd); // fd no longer needed - the dup'ed handles are sufficient
560
561 const std::string name("o2-sim-primary-server-device-runner");
562 const std::string path = installpath + "/" + name;
563 const std::string config = localconfig;
564
565 // copy all arguments into a common vector
566#ifdef SIM_RUN5
567 const int addNArgs = 12;
568#else
569 const int addNArgs = 11;
570#endif
571 const int Nargs = finalArgs.size() + addNArgs;
572 const char* arguments[Nargs];
573 arguments[0] = name.c_str();
574 arguments[1] = "--control";
575 arguments[2] = "static";
576 arguments[3] = "--id";
577 arguments[4] = "primary-server";
578 arguments[5] = "--mq-config";
579 arguments[6] = config.c_str();
580 arguments[7] = "--severity";
581 arguments[8] = "debug";
582 arguments[9] = "--color";
583 arguments[10] = "false"; // switch off colored output
584#ifdef SIM_RUN5
585 arguments[11] = "--isRun5";
586#endif
587 for (int i = 1; i < finalArgs.size(); ++i) {
588 arguments[addNArgs - 1 + i] = finalArgs[i];
589 }
590 arguments[Nargs - 1] = nullptr;
591 for (int i = 0; i < Nargs; ++i) {
592 if (arguments[i]) {
593 std::cerr << arguments[i] << "\n";
594 }
595 }
596 std::cerr << "$$$$\n";
597 auto r = execv(path.c_str(), (char* const*)arguments);
598 LOG(info) << "Starting the server"
599 << "\n";
600 if (r != 0) {
601 perror(nullptr);
602 }
603 return r;
604 } else {
605 gChildProcesses.push_back(pid);
606 setpgid(pid, pid);
607 close(pipe_serverdriver_fd[1]);
608 std::cout << "Spawning particle server on PID " << pid << "; Redirect output to " << getServerLogName() << "\n";
609
610 // A simple callback for distributed primary-chunk "events"
611 auto distributionCallback = [&conf, &externalpublishchannel](std::vector<int> const& v) {
612 std::stringstream str;
613 if (v.back() == -111) {
614 // message that server is initialized
616 return false; // silent
617 } else {
618 str << "EVENT " << v.back() << " DISTRIBUTED";
619 o2::simpubsub::publishMessage(externalpublishchannel, o2::simpubsub::simStatusString("O2SIM", "INFO", str.str()));
620 return true;
621 }
622 };
623 launchThreadMonitoringEvents(pipe_serverdriver_fd[0], "DISTRIBUTING EVENT : ", gDistributedEvents, distributionCallback);
624 }
625
626 // we wait until the particle server is initialized before constructing the worker
627 // since the worker needs an operating server to initialize
628 while (!gPrimServerIsInitialized) {
629 int status;
630 auto result = waitpid(gChildProcesses.back(), &status, WNOHANG);
631 if (result != 0) {
632 break; // exit this busy loop if the server process exited for some reason
633 }
634 sleep(1); // otherwise wait until server is initialized
635 }
636
637 auto internalfork = getenv("ALICE_SIMFORKINTERNAL");
638 if (internalfork) {
639 // forking will be done internally to profit from copy-on-write
640 nworkers = 1;
641 }
642 for (int id = 0; id < nworkers; ++id) {
643 // the workers
644 std::stringstream workerlogss;
645 workerlogss << getWorkerLogName() << id;
646
647 // the workers
648 std::stringstream workerss;
649 workerss << "worker" << id;
650
651 pid = fork();
652 if (pid == 0) {
653 int fd = open(workerlogss.str().c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
654 dup2(fd, 1); // make stdout go to file
655 dup2(fd, 2); // make stderr go to file - you may choose to not do this
656 // or perhaps send stderr to another file
657 close(fd); // fd no longer needed - the dup'ed handles are sufficient
658
659 const std::string name("o2-sim-device-runner");
660 const std::string path = installpath + "/" + name;
661
662 execl(path.c_str(), name.c_str(), "--control", "static", "--id", workerss.str().c_str(), "--config-key",
663 "worker", "--mq-config", localconfig.c_str(), "--severity", "info", (char*)nullptr);
664 return 0;
665 } else {
666 gChildProcesses.push_back(pid);
667 setpgid(pid, pid); // the worker processes will form their own group
668 std::cout << "Spawning sim worker " << id << " on PID " << pid
669 << "; Redirect output to " << workerlogss.str() << "\n";
670 }
671 }
672
673 // the hit merger
674 int pipe_mergerdriver_fd[2];
675 if (pipe(pipe_mergerdriver_fd) != 0) {
676 perror("problem in creating pipe");
677 }
678
679 pid = fork();
680
681 std::atomic<bool> shutdown_initiated = false;
682 if (pid == 0) {
683 int fd = open(getMergerLogName().c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
684 dup2(fd, 1); // make stdout go to file
685 dup2(fd, 2); // make stderr go to file - you may choose to not do this
686 // or perhaps send stderr to another file
687 close(fd); // fd no longer needed - the dup'ed handles are sufficient
688 close(pipe_mergerdriver_fd[0]);
689 setenv("ALICE_O2SIMMERGERTODRIVER_PIPE", std::to_string(pipe_mergerdriver_fd[1]).c_str(), 1);
690 const std::string name("o2-sim-hit-merger-runner");
691 const std::string path = installpath + "/" + name;
692 execl(path.c_str(), name.c_str(), "--control", "static", "--catch-signals", "0", "--id", "hitmerger", "--mq-config", localconfig.c_str(), "--color", "false",
693 (char*)nullptr);
694 return 0;
695 } else {
696 std::cout << "Spawning hit merger on PID " << pid << "; Redirect output to " << getMergerLogName() << "\n";
697 setpgid(pid, pid);
698 gChildProcesses.push_back(pid);
699 close(pipe_mergerdriver_fd[1]);
700
701 // A simple callback that determines if the simulation is complete and triggers
702 // a shutdown of all child processes. This appears to be more robust than leaving
703 // that decision upon the children (sometimes there are problems with that).
704 auto finishCallback = [&shutdown_initiated, &conf, &externalpublishchannel](std::vector<int> const& v) {
705 std::stringstream str;
706 str << "EVENT " << v.back() << " FINISHED " << gAskedEvents << " " << v.size();
707 o2::simpubsub::publishMessage(externalpublishchannel, o2::simpubsub::simStatusString("O2SIM", "INFO", str.str()));
708 if (gAskedEvents == v.size()) {
709 o2::simpubsub::publishMessage(externalpublishchannel, o2::simpubsub::simStatusString("O2SIM", "STATE", "DONE"));
710 if (!conf.asService()) {
711 LOG(info) << "SIMULATION IS DONE. INITIATING SHUTDOWN.";
712 if (!shutdown_initiated) {
713 shutdown_initiated = true;
714 for (auto p : gChildProcesses) {
715 if (killpg(p, 0) == 0) {
716 killpg(p, SIGTERM);
717 }
718 }
719 }
720 } else {
721 LOG(info) << "SIMULATION DONE. STAYING AS DAEMON.";
722 }
723 }
724 return true;
725 };
726
727 launchThreadMonitoringEvents(pipe_mergerdriver_fd[0], "EVENT FINISHED : ", gFinishedEvents, finishCallback);
728 }
729
730 // wait on merger (which when exiting completes the workflow)
731 auto mergerpid = gChildProcesses.back();
732
733 int status, cpid;
734 // wait just blocks and waits until any child returns; but we make sure to wait until merger is here
735 bool errored = false;
736 // wait at least until mergerpid is reaped
737 while ((cpid = wait(&status)) != -1) {
738 if (cpid == mergerpid) {
739 break; // Defer handling of mergerpid exit status until after the loop
740 }
741
742 if (WEXITSTATUS(status) || WIFSIGNALED(status)) {
743 if (!shutdown_initiated) {
744 LOG(info) << "Process " << cpid << " EXITED WITH CODE " << WEXITSTATUS(status) << " SIGNALED "
745 << WIFSIGNALED(status) << " SIGNAL " << WTERMSIG(status);
746
747 // we bring down all processes if one of them had problems or got a termination signal
748 // if (WTERMSIG(status) == SIGABRT || WTERMSIG(status) == SIGSEGV || WTERMSIG(status) == SIGBUS || WTERMSIG(status) == SIGTERM) {
749 LOG(info) << "Problem detected (or child received termination signal) ... shutting down whole system ";
750 for (auto p : gChildProcesses) {
751 LOG(info) << "TERMINATING " << p;
752 if (killpg(p, 0) == 0) {
753 killpg(p, SIGTERM); // <--- makes sure to shutdown "unknown" child pids via the group property
754 }
755 }
756 LOG(error) << "SHUTTING DOWN DUE TO SIGNALED EXIT IN COMPONENT " << cpid;
757 o2::simpubsub::publishMessage(externalpublishchannel, o2::simpubsub::simStatusString("O2SIM", "STATE", "FAILURE"));
758 errored = true;
759 }
760 }
761 }
762
763 // Handle mergerpid status separately
764 if (cpid == mergerpid) {
765 if (WIFEXITED(status)) {
766 if (WEXITSTATUS(status) != 0 || WEXITSTATUS(status) != 128) {
767 LOG(error) << "Merger process exited with abnormal exit status " << WEXITSTATUS(status);
768 errored = true;
769 }
770 } else if (WIFSIGNALED(status)) {
771 auto sig = WTERMSIG(status);
772 if (sig == SIGKILL || sig == SIGBUS || sig == SIGSEGV || sig == SIGABRT) {
773 LOG(error) << "Merger process terminated through abnormal signal " << WTERMSIG(status);
774 errored = true;
775 }
776 } else {
777 LOG(warning) << "Merger process exited with unexpected status.";
778 }
779 }
780
781 // This marks the actual end of the computation (since results are available)
782 LOG(info) << "Merger process " << mergerpid << " returned";
783 LOG(info) << "Simulation process took " << timer.RealTime() << " s";
784
785 if (!errored && !shutdown_initiated) {
786 shutdown_initiated = true;
787 // ordinary shutdown of the rest
788 for (auto p : gChildProcesses) {
789 if (p != mergerpid) {
790 LOG(info) << "SHUTTING DOWN CHILD PROCESS (normal thread)" << p;
791 if (killpg(p, 0) == 0) {
792 killpg(p, SIGTERM);
793 }
794 }
795 }
796 }
797
798 // Final shutdown section. Here we definitely wait on all children
799 // otherwise this breaks accounting in the /usr/bin/time command. But we install
800 // an asynchronous timeout thread which triggers an emergency kill after some seconds in order to not block.
802 while ((cpid = wait(&status))) {
803 if (cpid == -1) {
804 break;
805 }
806 }
807
808 LOG(debug) << "ShmManager operation " << o2::utils::ShmManager::Instance().isOperational() << "\n";
809
810 // forked sim workers can still be writing their scoring dumps after their parent exited
811 for (auto p : gChildProcesses) {
812 while (p != 0 && killpg(p, 0) == 0) {
813 usleep(100000);
814 }
815 }
816
817 // sum the Geant4 scoring meshes written by the individual workers
818 if (!errored && o2::conf::mergeG4ScoringDumps(".", conf.getNSimWorkers()) < 0) {
819 errored = true;
820 }
821
822 // do a quick check to see if simulation produced something reasonable
823 // (mainly useful for continuous integration / automated testing suite)
824 auto returncode = errored ? 1 : checkresult();
825 if (returncode == 0) {
826 LOG(info) << "SIMULATION RETURNED SUCCESFULLY";
827 }
828 cleanup();
829 return returncode;
830}
std::ostringstream debug
std::vector< std::string > header
int32_t i
Definition of the Names Generator class.
uint16_t pos
Definition RawData.h:3
uint16_t pid
Definition RawData.h:2
static std::string getMCKinematicsFileName(const std::string_view prefix=STANDARDSIMPREFIX)
Definition NameConf.h:46
static BasicCCDBManager & instance()
static SimConfig make()
Definition SimConfig.h:119
static SimConfig & Instance()
Definition SimConfig.h:112
static ShmManager & Instance()
Definition ShmManager.h:61
bool isOperational() const
Definition ShmManager.h:97
bool createGlobalSegment(int nsubsegments=1)
GLuint GLuint64EXT address
Definition glcorearb.h:5846
GLint GLsizei count
Definition glcorearb.h:399
GLuint64EXT * result
Definition glcorearb.h:5662
const GLdouble * v
Definition glcorearb.h:832
GLuint const GLchar * name
Definition glcorearb.h:781
GLdouble f
Definition glcorearb.h:310
GLsizei const GLchar *const * path
Definition glcorearb.h:3591
GLuint GLsizei const GLchar * message
Definition glcorearb.h:2517
GLboolean r
Definition glcorearb.h:1233
GLenum GLenum GLsizei len
Definition glcorearb.h:4232
GLuint id
Definition glcorearb.h:650
bpo::variables_map arguments
bool parseSimReconfigFromString(std::string const &argumentstring, SimReconfigData &config)
int mergeG4ScoringDumps(const std::string &directory, int expectedWorkers=0)
DeliveryType read(const std::string &str)
@ Completed
The channel was signaled it will not receive any data.
fair::mq::MessagePtr getMessage(ContainerT &&container, FairMQMemoryResource *targetResource=nullptr)
std::string simStatusString(std::string const &origin, std::string const &topic, std::string const &message)
std::string getPublishAddress(std::string const &base, int pid=getpid())
bool publishMessage(fair::mq::Channel &channel, std::string const &message)
fair::mq::Channel createPUBChannel(std::string const &address, std::string const &type="pub")
std::vector< std::string > listFiles(std::string const &dir, std::string const &searchpattern)
std::string getBuildInfo()
get information about build platform (for example OS and alidist release when used)
std::string gitRevision()
get O2 git commit used to build this
std::string fullVersion()
get full version information (official O2 release and git commit)
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
std::string filename()
bool isBusy()
void empty(int)
std::string getServerLogName()
std::vector< int > gFinishedEvents
std::vector< int > gChildProcesses
void cleanup()
int gAskedEvents
int checkresult()
void remove_tmp_files()
void launchWorkerListenerThread()
std::atomic< bool > gPrimServerIsInitialized
std::string getInternalControlAddress()
std::vector< int > gDistributedEvents
void launchShutdownThread()
void launchThreadMonitoringEvents(int pipefd, std::string text, std::vector< int > &eventcontainer, std::function< bool(std::vector< int > const &)> callback=[](std::vector< int > const &) { return true;})
std::string getMergerLogName()
void sighandler(int sig)
std::string getWorkerLogName()
std::vector< char * > checkArgs(int argc, char *argv[])
void launchControlThread()
std::string getControlAddress()
TODO: Make this a base class of SimConfigData?
Definition SimConfig.h:205
a BaseHeader with state information from the source
a move-only header stack with serialized headers This is the flat buffer where all the headers in a m...
Definition Stack.h:33
#define main
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
const std::string str
uint64_t const void const *restrict const msg
Definition x9.h:153