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
52
53std::string getServerLogName()
54{
55 auto& conf = o2::conf::SimConfig::Instance();
56 std::stringstream str;
57 str << conf.getOutPrefix() << "_serverlog";
58 return str.str();
59}
60
61std::string getWorkerLogName()
62{
63 auto& conf = o2::conf::SimConfig::Instance();
64 std::stringstream str;
65 str << conf.getOutPrefix() << "_workerlog";
66 return str.str();
67}
68
69std::string getMergerLogName()
70{
71 auto& conf = o2::conf::SimConfig::Instance();
72 std::stringstream str;
73 str << conf.getOutPrefix() << "_mergerlog";
74 return str.str();
75}
76
78{
79 // remove all (known) socket files in /tmp
80 // using the naming convention /tmp/o2sim-.*PID
81 std::stringstream searchstr;
82 searchstr << "o2sim-.*-" << getpid() << "$";
83 auto filenames = o2::utils::listFiles("/tmp/", searchstr.str());
84 // remove those files
85 for (auto& fn : filenames) {
86 try {
87 std::filesystem::remove(std::filesystem::path(fn));
88 } catch (...) {
89 LOG(warn) << "Couldn't remove tmp file " << fn;
90 }
91 }
92}
93
94void cleanup()
95{
96 auto& conf = o2::conf::SimConfig::Instance();
97 if (conf.forwardKine()) {
98 auto factory = fair::mq::TransportFactory::CreateTransportFactory("zeromq");
99 auto forwardchannel = fair::mq::Channel{"kineforward", "pair", factory};
100 auto address = std::string{"ipc:///tmp/o2sim-hitmerger-kineforward-"} + std::to_string(getpid());
101 forwardchannel.Bind(address.c_str());
102 forwardchannel.Validate();
103 fair::mq::Parts parts;
104 fair::mq::MessagePtr payload(forwardchannel.NewMessage());
107 auto channelAlloc = o2::pmr::getTransportAllocator(forwardchannel.Transport());
108 auto header = o2::pmr::getMessage(o2::header::Stack{channelAlloc, sih});
109 parts.AddPart(std::move(header));
110 parts.AddPart(std::move(payload));
111 int timeoutinMS = 1000; // block for 1s max (other side might have disconnected already)
112 if (forwardchannel.Send(parts, timeoutinMS) > 0) {
113 LOGP(info, "SENDING END-OF-STREAM TO PROXY AT {}", address.c_str());
114 } else {
115 LOGP(warn, "SENDING END-OF-STREAM TIMED OUT; PEER PROBABLY NO LONGER CONNECTED");
116 }
117 }
120
121 // special mode in which we dump the output from various
122 // log files to terminal (mainly interesting for CI mode)
123 if (getenv("ALICE_O2SIM_DUMPLOG")) {
124 std::cerr << "------------- START OF EVENTSERVER LOG ----------" << std::endl;
125 std::stringstream catcommand1;
126 catcommand1 << "cat " << getServerLogName() << ";";
127 if (system(catcommand1.str().c_str()) != 0) {
128 LOG(warn) << "error executing system call";
129 }
130
131 std::cerr << "------------- START OF SIM WORKER(S) LOG --------" << std::endl;
132 std::stringstream catcommand2;
133 catcommand2 << "cat " << getWorkerLogName() << "*;";
134 if (system(catcommand2.str().c_str()) != 0) {
135 LOG(warn) << "error executing system call";
136 }
137
138 std::cerr << "------------- START OF MERGER LOG ---------------" << std::endl;
139 std::stringstream catcommand3;
140 catcommand3 << "cat " << getMergerLogName() << ";";
141 if (system(catcommand3.str().c_str()) != 0) {
142 LOG(warn) << "error executing system call";
143 }
144 }
145}
146
147// quick cross check of simulation output
149{
150 int errors = 0;
151 // We can put more or less complex things
152 // here.
153 auto& conf = o2::conf::SimConfig::Instance();
154 if (!conf.writeToDisc()) {
155 return 0;
156 }
157 // easy check: see if we have number of entries in output tree == number of events asked
158 std::string filename = o2::base::NameConf::getMCKinematicsFileName(conf.getOutPrefix().c_str());
159 TFile f(filename.c_str(), "OPEN");
160 if (f.IsZombie()) {
161 LOG(warn) << "Kinematics file corrupted or does not exist";
162 return 1;
163 }
164 auto tr = static_cast<TTree*>(f.Get("o2sim"));
165 if (!tr) {
166 errors++;
167 } else {
168 if (!conf.isFilterOutNoHitEvents()) {
169 if (tr->GetEntries() != conf.getNEvents()) {
170 LOG(warn) << "There are fewer events in the output than asked";
171 }
172 }
173 }
174 // add more simple checks
175
176 return errors;
177}
178
179// ---> THE FOLLOWING CAN BE PUT INTO A "STATE" STRUCT
180std::vector<int> gChildProcesses; // global vector of child pids
181// record distributed events in a container
182std::vector<int> gDistributedEvents;
183// record finished events in a container
184std::vector<int> gFinishedEvents;
186std::atomic<bool> gPrimServerIsInitialized = false;
187
188std::string getControlAddress()
189{
190 std::stringstream controlsocketname;
191 controlsocketname << "ipc:///tmp/o2sim-control-" << getpid();
192 return controlsocketname.str();
193}
195{
196 // creates names for an internal-only socket
197 // hashing to distinguish from more "public" sockets
198 std::hash<std::string> hasher;
199 std::stringstream str;
200 str << "o2sim-internal_" << getpid();
201 std::string tmp(std::to_string(hasher(str.str())));
202 std::stringstream controlsocketname;
203 controlsocketname << "ipc:///tmp/" << tmp.substr(0, 10) << "_" << getpid();
204 return controlsocketname.str();
205}
206
207bool isBusy()
208{
209 if (gFinishedEvents.size() != gAskedEvents) {
210 return true;
211 }
212 return false;
213}
214
215// launches a thread that listens for control command from outside
216// or that propagates control strings to all children
218{
219 static std::vector<std::thread> threads;
220 auto controladdress = getControlAddress();
221 auto internalcontroladdress = getInternalControlAddress();
222 LOG(info) << "Control address is: " << controladdress;
223 setenv("ALICE_O2SIMCONTROL", internalcontroladdress.c_str(), 1);
224
225 auto lambda = [controladdress, internalcontroladdress]() {
226 auto factory = fair::mq::TransportFactory::CreateTransportFactory("zeromq");
227
228 // used for internal distribution of control commands
229 auto internalchannel = fair::mq::Channel{"o2sim-internal", "pub", factory};
230 internalchannel.Bind(internalcontroladdress);
231 internalchannel.Validate();
232 std::unique_ptr<fair::mq::Message> message(internalchannel.NewMessage());
233
234 // the channel with which outside entities can control this simulator
235 auto outsidechannel = fair::mq::Channel{"o2sim-control", "rep", factory};
236 outsidechannel.Bind(controladdress);
237 outsidechannel.Validate();
238 std::unique_ptr<fair::mq::Message> request(outsidechannel.NewMessage());
239
240 bool keepgoing = true;
241 while (keepgoing) {
242 outsidechannel.Init();
243 outsidechannel.Bind(controladdress);
244 outsidechannel.Validate();
245 if (outsidechannel.Receive(request) > 0) {
246 std::string command(reinterpret_cast<char const*>(request->GetData()), request->GetSize());
247 LOG(info) << "Control message: " << command << " received ";
248 int code = -1;
249 if (isBusy()) {
250 code = 1; // code = 1 --> busy
251 std::unique_ptr<fair::mq::Message> reply(outsidechannel.NewSimpleMessage(code));
252 outsidechannel.Send(reply);
253 } else {
254 code = 0; // code = 0 --> ok
255
257 auto success = o2::conf::parseSimReconfigFromString(command, reconfig);
258 if (!success) {
259 LOG(warn) << "CONTROL REQUEST COULD NOT BE PARSED";
260 code = 2; // code = 2 --> error with request data
261 }
262 std::unique_ptr<fair::mq::Message> reply(outsidechannel.NewSimpleMessage(code));
263 outsidechannel.Send(reply);
264
265 if (code == 0) {
266 gAskedEvents = reconfig.nEvents;
267 gDistributedEvents.clear();
268 gFinishedEvents.clear();
269 // forward request from outside to all internal processes
270 internalchannel.Send(request);
271 keepgoing = !reconfig.stop;
272 }
273 }
274 }
275 }
276 };
277 threads.push_back(std::thread(lambda));
278 threads.back().detach();
279}
280
281// launches a thread that listens for control command from outside
282// or that propagates control strings to all children
284{
285 static std::vector<std::thread> threads;
286 auto lambda = []() {
287 auto factory = fair::mq::TransportFactory::CreateTransportFactory("zeromq");
288
289 auto listenchannel = fair::mq::Channel{"channel0", "sub", factory};
290 listenchannel.Init();
291 std::stringstream address;
292 address << "ipc:///tmp/o2sim-worker-notifications-" << getpid();
293 listenchannel.Connect(address.str());
294 listenchannel.Validate();
295 std::unique_ptr<fair::mq::Message> message(listenchannel.NewMessage());
296
297 while (true) {
298 if (listenchannel.Receive(message) > 0) {
299 std::string msg(reinterpret_cast<char const*>(message->GetData()), message->GetSize());
300 LOG(info) << "Worker message: " << msg;
301 }
302 }
303 };
304 threads.push_back(std::thread(lambda));
305 threads.back().detach();
306}
307
308// monitors a certain incoming event pipes and displays new information
309// gives possibility to exec a callback at these events
311 int pipefd, std::string text, std::vector<int>& eventcontainer,
312 std::function<bool(std::vector<int> const&)> callback = [](std::vector<int> const&) { return true; })
313{
314 static std::vector<std::thread> threads;
315 auto lambda = [pipefd, text, callback, &eventcontainer]() {
316 int eventcounter; // event id or some other int message
317 while (1) {
318 ssize_t count = read(pipefd, &eventcounter, sizeof(eventcounter));
319 if (count == -1) {
320 LOG(info) << "ERROR READING";
321 if (errno == EINTR) {
322 continue;
323 } else {
324 return;
325 }
326 } else if (count == 0) {
327 break;
328 } else {
329 eventcontainer.push_back(eventcounter);
330 if (callback(eventcontainer)) {
331 LOG(info) << text.c_str() << eventcounter;
332 }
333 }
334 };
335 };
336 threads.push_back(std::thread(lambda));
337 threads.back().detach();
338}
339
341{
342 static std::vector<std::thread> threads;
343 auto lambda = []() {
344 // once started ... we are waiting for some seconds
345 // then **force** shutdown all remaining children by killing them.
346 // This is to make sure that the process does not hang during a final wait
347 // and interrupted/blocked signal delivery.
348
349 struct timespec initial, remaining;
350 initial.tv_sec = 5;
351 // wait for specified time ... (and account for possible signal interruptions)
352 while (nanosleep(&initial, &remaining) == -1 && remaining.tv_sec > 0) {
353 initial = remaining;
354 }
355 LOG(info) << "Shutdown timer expired ... force killing remaining children";
356 for (auto p : gChildProcesses) {
357 if (p != 0 && killpg(p, 0) == 0) { // see if process still exists
358 killpg(p, SIGKILL);
359 }
360 }
361 };
362 threads.push_back(std::thread(lambda));
363 threads.back().detach();
364}
365
366void empty(int) {}
367
368// signal handler for graceful exit
369void sighandler(int sig)
370{
371 if (sig == SIGINT || sig == SIGTERM) {
372 signal(sig, empty); // ignore further deliveries of these signals
373 LOG(info) << "o2-sim driver: Signal caught ... clean up and exit (please be patient)";
374 // forward signal to all children
375 for (auto& pid : gChildProcesses) {
376 killpg(pid, sig);
377 }
378 cleanup();
379
380 // make sure everyone is really shutting down
381 int status, cpid;
383 while ((cpid = wait(&status))) {
384 if (cpid == -1) {
385 break;
386 }
387 }
388
389 exit(1); // exiting upon external signal is abnormal so exit code != 0
390 }
391}
392
393// We do some early checks on the arguments passed. In particular we fix
394// missing timestamps for consistent application in all sub-processes. An empty
395// vector is returned upon errors.
396std::vector<char*> checkArgs(int argc, char* argv[])
397{
398 auto conf = o2::conf::SimConfig::make();
399 std::vector<std::string> modifiedArgs;
400#ifdef SIM_RUN5
401 conf.setRun5();
402#endif
403 if (conf.resetFromArguments(argc, argv)) {
404 for (int i = 0; i < argc; ++i) {
405 modifiedArgs.push_back(argv[i]);
406 }
407
408 // Check the run and the time arguments and enforce consistency.
409 // This is important as queries to CCDB are done using the timestamp.
410 if (conf.getRunNumber() != -1) {
411 // if we have a run number we should fix or check the timestamp
412
413 // fetch the actual timestamp ranges for this run
414 auto& ccdbmgr = o2::ccdb::BasicCCDBManager::instance();
415 auto soreor = ccdbmgr.getRunDuration(conf.getRunNumber());
416 auto timestamp = conf.getTimestamp();
417 if (conf.getConfigData().mTimestampMode == o2::conf::TimeStampMode::kNow) {
418 timestamp = soreor.first;
419 LOG(info) << "Fixing timestamp to " << timestamp << " based on run number";
420 modifiedArgs.push_back("--timestamp");
421 modifiedArgs.push_back(std::to_string(timestamp));
422 } else if (conf.getConfigData().mTimestampMode == o2::conf::TimeStampMode::kManual && (timestamp < soreor.first || timestamp > soreor.second)) {
423 LOG(fatal) << "The given timestamp " << timestamp << " is incompatible with the given run number " << conf.getRunNumber() << " starting at " << soreor.first << " and ending at " << soreor.second;
424 }
425 }
426 }
427 std::vector<char*> final(modifiedArgs.size(), nullptr);
428 for (int i = 0; i < modifiedArgs.size(); ++i) {
429 final[i] = new char[modifiedArgs[i].size() + 1];
430 strcpy(final[i], modifiedArgs[i].c_str());
431 }
432 return final;
433}
434
435// helper executable to launch all the devices/processes
436// for parallel simulation
437int main(int argc, char* argv[])
438{
439 // for the workers, which load Geant4 by dlopen; glibc takes the static TLS reserve only at program start
441 LOG(info) << "This is o2-sim version " << o2::fullVersion() << " (" << o2::gitRevision() << ")";
442 LOG(info) << o2::getBuildInfo();
443
444 signal(SIGINT, sighandler);
445 signal(SIGTERM, sighandler);
446 // we enable the forked version of the code by default
447 setenv("ALICE_SIMFORKINTERNAL", "ON", 1);
448
449 // force execution as own process group
450 if (setpgid(0, 0) == -1) {
451 perror("setpgid");
452 exit(1);
453 }
454
455 TStopwatch timer;
456 timer.Start();
457 auto o2env = getenv("O2_ROOT");
458 if (!o2env) {
459 LOG(fatal) << "O2_ROOT environment not defined";
460 }
461 std::string rootpath(o2env);
462 std::string installpath = rootpath + "/bin";
463
464 // copy topology file to working dir and update ports
465 std::stringstream configss;
466 configss << rootpath << "/share/config/o2simtopology_template.json";
467 auto localconfig = std::string("o2simtopology_") + std::to_string(getpid()) + std::string(".json");
468
469 // need to add pid to channel urls to allow simultaneous deploys!
470 // we simply insert the PID into the topology template
471 std::ifstream in(configss.str());
472 std::ofstream out(localconfig);
473 std::string wordToReplace("#PID#");
474 std::string wordToReplaceWith = std::to_string(getpid());
475 std::string line;
476 size_t len = wordToReplace.length();
477 while (std::getline(in, line)) {
478 size_t pos = line.find(wordToReplace);
479 if (pos != std::string::npos) {
480 line.replace(pos, len, wordToReplaceWith);
481 }
482 out << line << '\n';
483 }
484 in.close();
485 out.close();
486
487 // create a channel for outside event notifications --> factor out into common function
488 // auto factory = fair::mq::TransportFactory::CreateTransportFactory("zeromq");
489 auto externalpublishchannel = o2::simpubsub::createPUBChannel(o2::simpubsub::getPublishAddress("o2sim-notifications"));
490
491 // check initial arguments and complete
492 auto finalArgs = checkArgs(argc, argv);
493 if (finalArgs.size() == 0) {
494 return 1;
495 }
496
497 auto& conf = o2::conf::SimConfig::Instance();
498#ifdef SIM_RUN5
499 conf.setRun5();
500#endif
501 if (!conf.resetFromArguments(finalArgs.size(), &finalArgs[0])) {
502 return 1;
503 }
504 // in case of zero events asked (only setup geometry etc) we just call the non-distributed version
505 // (otherwise we would need to add more synchronization between the actors)
506 if (conf.getNEvents() <= 0 && !conf.asService()) {
507 LOG(info) << "No events to be simulated; Switching to non-distributed mode";
508 const int Nargs = finalArgs.size() + 1;
509#ifdef SIM_RUN5
510 std::string name("o2-sim-serial-run5");
511#else
512 std::string name("o2-sim-serial");
513#endif
514 const char* arguments[Nargs];
515 arguments[0] = name.c_str();
516 for (int i = 1; i < finalArgs.size(); ++i) {
517 arguments[i] = finalArgs[i];
518 }
519 arguments[finalArgs.size()] = nullptr;
520 std::string path = installpath + "/" + name;
521 auto r = execv(path.c_str(), (char* const*)arguments);
522 if (r != 0) {
523 perror(nullptr);
524 }
525 return r;
526 }
527
528 gAskedEvents = conf.getNEvents();
529 if (conf.asService()) {
531 // launchWorkerListenerThread();
532 }
533
534 // we create the global shared mem pool; just enough to serve
535 // n simulation workers
536 int nworkers = conf.getNSimWorkers();
537 setenv("ALICE_NSIMWORKERS", std::to_string(nworkers).c_str(), 1);
538 LOG(info) << "Running with " << nworkers << " sim workers ";
539 if (conf.getIsMT()) {
540 if (conf.getMCEngine() != "TGeant4") {
541 LOG(fatal) << "Multithreaded mode (--isMT) is only available with TGeant4";
542 }
543 // one worker process with a Geant4 transport thread per sim worker
544 setenv("ALICE_O2SIM_MT", "ON", 1);
545 LOG(info) << "Multithreaded Geant4 with " << nworkers << " transport threads";
546 }
547
549
550 // we can try to disable it here
551 if (getenv("ALICE_NOSIMSHM")) {
553 }
554
555 int pipe_serverdriver_fd[2];
556 if (pipe(pipe_serverdriver_fd) != 0) {
557 perror("problem in creating pipe");
558 }
559
560 // the server
561 int pid = fork();
562 if (pid == 0) {
563 int fd = open(getServerLogName().c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
564 setenv("ALICE_O2SIMSERVERTODRIVER_PIPE", std::to_string(pipe_serverdriver_fd[1]).c_str(), 1);
565
566 dup2(fd, 1); // make stdout go to file
567 dup2(fd, 2); // make stderr go to file - you may choose to not do this
568 // or perhaps send stderr to another file
569 close(pipe_serverdriver_fd[0]);
570 close(fd); // fd no longer needed - the dup'ed handles are sufficient
571
572 const std::string name("o2-sim-primary-server-device-runner");
573 const std::string path = installpath + "/" + name;
574 const std::string config = localconfig;
575
576 // copy all arguments into a common vector
577#ifdef SIM_RUN5
578 const int addNArgs = 12;
579#else
580 const int addNArgs = 11;
581#endif
582 const int Nargs = finalArgs.size() + addNArgs;
583 const char* arguments[Nargs];
584 arguments[0] = name.c_str();
585 arguments[1] = "--control";
586 arguments[2] = "static";
587 arguments[3] = "--id";
588 arguments[4] = "primary-server";
589 arguments[5] = "--mq-config";
590 arguments[6] = config.c_str();
591 arguments[7] = "--severity";
592 arguments[8] = "debug";
593 arguments[9] = "--color";
594 arguments[10] = "false"; // switch off colored output
595#ifdef SIM_RUN5
596 arguments[11] = "--isRun5";
597#endif
598 for (int i = 1; i < finalArgs.size(); ++i) {
599 arguments[addNArgs - 1 + i] = finalArgs[i];
600 }
601 arguments[Nargs - 1] = nullptr;
602 for (int i = 0; i < Nargs; ++i) {
603 if (arguments[i]) {
604 std::cerr << arguments[i] << "\n";
605 }
606 }
607 std::cerr << "$$$$\n";
608 auto r = execv(path.c_str(), (char* const*)arguments);
609 LOG(info) << "Starting the server"
610 << "\n";
611 if (r != 0) {
612 perror(nullptr);
613 }
614 return r;
615 } else {
616 gChildProcesses.push_back(pid);
617 setpgid(pid, pid);
618 close(pipe_serverdriver_fd[1]);
619 std::cout << "Spawning particle server on PID " << pid << "; Redirect output to " << getServerLogName() << "\n";
620
621 // A simple callback for distributed primary-chunk "events"
622 auto distributionCallback = [&conf, &externalpublishchannel](std::vector<int> const& v) {
623 std::stringstream str;
624 if (v.back() == -111) {
625 // message that server is initialized
627 return false; // silent
628 } else {
629 str << "EVENT " << v.back() << " DISTRIBUTED";
630 o2::simpubsub::publishMessage(externalpublishchannel, o2::simpubsub::simStatusString("O2SIM", "INFO", str.str()));
631 return true;
632 }
633 };
634 launchThreadMonitoringEvents(pipe_serverdriver_fd[0], "DISTRIBUTING EVENT : ", gDistributedEvents, distributionCallback);
635 }
636
637 // we wait until the particle server is initialized before constructing the worker
638 // since the worker needs an operating server to initialize
639 while (!gPrimServerIsInitialized) {
640 int status;
641 auto result = waitpid(gChildProcesses.back(), &status, WNOHANG);
642 if (result != 0) {
643 break; // exit this busy loop if the server process exited for some reason
644 }
645 sleep(1); // otherwise wait until server is initialized
646 }
647
648 int mergerpid = -1;
649 // the hit merger, spawned before the workers so that it listens when they send hits
650 int pipe_mergerdriver_fd[2];
651 if (pipe(pipe_mergerdriver_fd) != 0) {
652 perror("problem in creating pipe");
653 }
654
655 pid = fork();
656
657 std::atomic<bool> shutdown_initiated = false;
658 std::function<bool(std::vector<int> const&)> finishCallback;
659 if (pid == 0) {
660 int fd = open(getMergerLogName().c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
661 dup2(fd, 1); // make stdout go to file
662 dup2(fd, 2); // make stderr go to file - you may choose to not do this
663 // or perhaps send stderr to another file
664 close(fd); // fd no longer needed - the dup'ed handles are sufficient
665 close(pipe_mergerdriver_fd[0]);
666 setenv("ALICE_O2SIMMERGERTODRIVER_PIPE", std::to_string(pipe_mergerdriver_fd[1]).c_str(), 1);
667 const std::string name("o2-sim-hit-merger-runner");
668 const std::string path = installpath + "/" + name;
669 execl(path.c_str(), name.c_str(), "--control", "static", "--catch-signals", "0", "--id", "hitmerger", "--mq-config", localconfig.c_str(), "--color", "false",
670 (char*)nullptr);
671 return 0;
672 } else {
673 std::cout << "Spawning hit merger on PID " << pid << "; Redirect output to " << getMergerLogName() << "\n";
674 setpgid(pid, pid);
675 gChildProcesses.push_back(pid);
676 mergerpid = pid;
677 close(pipe_mergerdriver_fd[1]);
678
679 // A simple callback that determines if the simulation is complete and triggers
680 // a shutdown of all child processes. This appears to be more robust than leaving
681 // that decision upon the children (sometimes there are problems with that).
682 finishCallback = [&shutdown_initiated, &conf, &externalpublishchannel](std::vector<int> const& v) {
683 std::stringstream str;
684 str << "EVENT " << v.back() << " FINISHED " << gAskedEvents << " " << v.size();
685 o2::simpubsub::publishMessage(externalpublishchannel, o2::simpubsub::simStatusString("O2SIM", "INFO", str.str()));
686 if (gAskedEvents == v.size()) {
687 o2::simpubsub::publishMessage(externalpublishchannel, o2::simpubsub::simStatusString("O2SIM", "STATE", "DONE"));
688 if (!conf.asService()) {
689 LOG(info) << "SIMULATION IS DONE. INITIATING SHUTDOWN.";
690 if (!shutdown_initiated) {
691 shutdown_initiated = true;
692 for (auto p : gChildProcesses) {
693 if (killpg(p, 0) == 0) {
694 killpg(p, SIGTERM);
695 }
696 }
697 }
698 } else {
699 LOG(info) << "SIMULATION DONE. STAYING AS DAEMON.";
700 }
701 }
702 return true;
703 };
704 }
705
706 auto internalfork = getenv("ALICE_SIMFORKINTERNAL");
707 if (internalfork) {
708 // forking will be done internally to profit from copy-on-write
709 nworkers = 1;
710 }
711 for (int id = 0; id < nworkers; ++id) {
712 // the workers
713 std::stringstream workerlogss;
714 workerlogss << getWorkerLogName() << id;
715
716 // the workers
717 std::stringstream workerss;
718 workerss << "worker" << id;
719
720 pid = fork();
721 if (pid == 0) {
722 int fd = open(workerlogss.str().c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
723 dup2(fd, 1); // make stdout go to file
724 dup2(fd, 2); // make stderr go to file - you may choose to not do this
725 // or perhaps send stderr to another file
726 close(fd); // fd no longer needed - the dup'ed handles are sufficient
727
728 const std::string name("o2-sim-device-runner");
729 const std::string path = installpath + "/" + name;
730
731 execl(path.c_str(), name.c_str(), "--control", "static", "--id", workerss.str().c_str(), "--config-key",
732 "worker", "--mq-config", localconfig.c_str(), "--severity", "info", (char*)nullptr);
733 return 0;
734 } else {
735 gChildProcesses.push_back(pid);
736 setpgid(pid, pid); // the worker processes will form their own group
737 std::cout << "Spawning sim worker " << id << " on PID " << pid
738 << "; Redirect output to " << workerlogss.str() << "\n";
739 }
740 }
741
742 // monitor the merger only once all child pids are known to the shutdown callback
743 launchThreadMonitoringEvents(pipe_mergerdriver_fd[0], "EVENT FINISHED : ", gFinishedEvents, finishCallback);
744
745 // wait on merger (which when exiting completes the workflow)
746 int status, cpid;
747 // wait just blocks and waits until any child returns; but we make sure to wait until merger is here
748 bool errored = false;
749 // wait at least until mergerpid is reaped
750 while ((cpid = wait(&status)) != -1) {
751 if (cpid == mergerpid) {
752 break; // Defer handling of mergerpid exit status until after the loop
753 }
754
755 if (WEXITSTATUS(status) || WIFSIGNALED(status)) {
756 if (!shutdown_initiated) {
757 LOG(info) << "Process " << cpid << " EXITED WITH CODE " << WEXITSTATUS(status) << " SIGNALED "
758 << WIFSIGNALED(status) << " SIGNAL " << WTERMSIG(status);
759
760 // we bring down all processes if one of them had problems or got a termination signal
761 // if (WTERMSIG(status) == SIGABRT || WTERMSIG(status) == SIGSEGV || WTERMSIG(status) == SIGBUS || WTERMSIG(status) == SIGTERM) {
762 LOG(info) << "Problem detected (or child received termination signal) ... shutting down whole system ";
763 for (auto p : gChildProcesses) {
764 LOG(info) << "TERMINATING " << p;
765 if (killpg(p, 0) == 0) {
766 killpg(p, SIGTERM); // <--- makes sure to shutdown "unknown" child pids via the group property
767 }
768 }
769 LOG(error) << "SHUTTING DOWN DUE TO SIGNALED EXIT IN COMPONENT " << cpid;
770 o2::simpubsub::publishMessage(externalpublishchannel, o2::simpubsub::simStatusString("O2SIM", "STATE", "FAILURE"));
771 errored = true;
772 }
773 }
774 }
775
776 // Handle mergerpid status separately
777 if (cpid == mergerpid) {
778 if (WIFEXITED(status)) {
779 if (WEXITSTATUS(status) != 0 && WEXITSTATUS(status) != 128) {
780 LOG(error) << "Merger process exited with abnormal exit status " << WEXITSTATUS(status);
781 errored = true;
782 }
783 } else if (WIFSIGNALED(status)) {
784 auto sig = WTERMSIG(status);
785 if (sig == SIGKILL || sig == SIGBUS || sig == SIGSEGV || sig == SIGABRT) {
786 LOG(error) << "Merger process terminated through abnormal signal " << WTERMSIG(status);
787 errored = true;
788 }
789 } else {
790 LOG(warning) << "Merger process exited with unexpected status.";
791 }
792 }
793
794 // This marks the actual end of the computation (since results are available)
795 LOG(info) << "Merger process " << mergerpid << " returned";
796 LOG(info) << "Simulation process took " << timer.RealTime() << " s";
797
798 if (!errored && !shutdown_initiated) {
799 shutdown_initiated = true;
800 // ordinary shutdown of the rest
801 for (auto p : gChildProcesses) {
802 if (p != mergerpid) {
803 LOG(info) << "SHUTTING DOWN CHILD PROCESS (normal thread)" << p;
804 if (killpg(p, 0) == 0) {
805 killpg(p, SIGTERM);
806 }
807 }
808 }
809 }
810
811 // Final shutdown section. Here we definitely wait on all children
812 // otherwise this breaks accounting in the /usr/bin/time command. But we install
813 // an asynchronous timeout thread which triggers an emergency kill after some seconds in order to not block.
815 while ((cpid = wait(&status))) {
816 if (cpid == -1) {
817 break;
818 }
819 }
820
821 LOG(debug) << "ShmManager operation " << o2::utils::ShmManager::Instance().isOperational() << "\n";
822
823 // forked sim workers can still be writing their scoring dumps after their parent exited
824 for (auto p : gChildProcesses) {
825 while (p != 0 && killpg(p, 0) == 0) {
826 usleep(100000);
827 }
828 }
829
830 // sum the Geant4 scoring meshes written by the individual workers
831 if (!errored && o2::conf::mergeG4ScoringDumps(".", conf.getIsMT() ? 1 : conf.getNSimWorkers()) < 0) {
832 errored = true;
833 }
834
835 // do a quick check to see if simulation produced something reasonable
836 // (mainly useful for continuous integration / automated testing suite)
837 auto returncode = errored ? 1 : checkresult();
838 if (returncode == 0) {
839 LOG(info) << "SIMULATION RETURNED SUCCESFULLY";
840 }
841 cleanup();
842 return returncode;
843}
std::ostringstream debug
std::vector< std::string > header
int32_t i
configStandalone Reconstruction rec_tpc multiplier for the combined cluster track error during track following area in cm for the search of for z only used if decrease removal by when above this local occupancy rowx Shift of TPC revert cluster flag edge bit distance to edge exceeds this ZOffset for secondary multiple of radius max P of electron K BB bands crossing max P of electron p BB bands crossing max P of electron d BB bands crossing max P of electron t BB bands crossing min accepted dEdxMaxTPC of the track Additive extra cluster error for Y2 if edge flag set Additive extra cluster error for Y2 if single set Additive extra cluster error for Y2 if or single set Additive extra cluster error for Z2 if or single set Extra errors to add to track when crossing depending on addErrorsCECrossing Inner portion of y range in sector that is not used in searching for extrapolated track candidates factor2YS for track merging factor2K for track merging Margin in cm of Y position when rejecting edge clusters based on uncorrected track Y Max fraction of shared clusters for tracklet Except pad from cluster finding if total number of charges in a fragment is above this Except pad from cluster finding if number of consecutive charges in a fragment is above this Threshold where a timebin is considered disabling the noisy pad check for that pad Treat signal above this minimum as saturated Smoothing factor for the exponential Highly Ionising Particle tail filter Number of timebins to use for the averaging Add additional custom track errors when crossing relative as fraction of Do N fit passes in final fit of< 0:reject at max -n hits") AddOptionRTC(rejectIFCLowRadiusCluster, uint8_t, 1, "", 0, "Reject clusters that get the IFC mask error during refit") AddOptionRTC(dEdxTruncLow, uint8_t, 2, "", 0, "Low truncation threshold, fraction of 128") AddOptionRTC(dEdxTruncHigh, uint8_t, 77, "", 0, "High truncation threshold, fraction of 128") AddOptionRTC(extrapolationTracking, int8_t, 1, "", 0, "Enable Extrapolation Tracking(prolong tracks to adjacent sectors to find short segments)") AddOptionRTC(disableRefitAttachment, uint8_t, 0, "", 0, "Bitmask to disable certain attachment steps during refit(1:attachment, 2:propagation, 4:loop following, 8:mirroring)") AddOptionRTC(rejectionStrategy, uint8_t, o2::gpu::GPUSettings::RejectionStrategyA, "", 0, "Enable rejection of TPC clusters for compression(0=no, 1=strategy A, 2=strategy B)") AddOptionRTC(mergeLoopersAfterburner, uint8_t, 1, "", 0, "Run afterburner for additional looper merging") AddOptionRTC(compressionTypeMask, uint8_t, o2::gpu::GPUSettings::CompressionFull, "", 0, "TPC Compression mode bits(1=truncate charge/width LSB, 2=differences, 4=track-model)") AddOptionRTC(compressionSortOrder, uint8_t, o2::gpu::GPUSettings::SortTime, "", 0, "Sort order of TPC compression(0=time, 1=pad, 2=Z-time-pad, 3=Z-pad-time, 4=no sorting(use incoming order))") AddOptionRTC(sigBitsCharge, uint8_t, 4, "", 0, "Number of significant bits for TPC cluster charge in compression mode 1") AddOptionRTC(sigBitsWidth, uint8_t, 3, "", 0, "Number of significant bits for TPC cluster width in compression mode 1") AddOptionRTC(dropLoopers, uint8_t, 0, "", 0, "Drop looping tracks starting from second loop") AddOptionRTC(mergerInterpolateErrors, uint8_t, 1, "", 0, "Use interpolation instead of extrapolation for chi2 based cluster rejection") AddOptionRTC(mergerInterpolateRejectAlsoOnCurrentPosition, uint8_t, 1, "", 0, "When using mergerInterpolateErrors, reject based on chi2 twice computed with interpolated and current track position starting from NDF > mergerNonInterpolateRejectMinNDF Merge tracks accross the central electrode Enable PID response Add sub threshold clusters in TPC dEdx computation OR mask of TPC flags that will reject the cluster in alternative dEdx Reject edge clusters based on uncorrected track Y during track fit Increase decrease removal by when on this number of pads from the edge h configStandalone Reconstruction rec_trd Max chi2 for TRD tracklets to be matched to a track Chi2 cut for strict matching mode Number of sigmas for ITS TPC track time error estimate Addition to search road around track prolongation along Y in cm Optimal tracklet rphi resolution in maximum chi2 per attached tracklet for TRD tracks take into account the full likelihood of the slope including exponential tails Tracks with less attached tracklets are discarded after the tracking Post trigger Pile up integration time in BCs fits all iterations but the last one in projections Use material lookup table for TPC refit Apply workarounds to avoid cyclic merge should not be needed tpc dyn configStandalone Processing proc_rtc bool
char error[CURL_ERROR_SIZE]
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:62
bool isOperational() const
Definition ShmManager.h:99
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
bool setStaticTLSReserve(unsigned long reserve=Geant4StaticTLSReserve)
Sets the static TLS reserve in the environment, for the programs started from here....
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