ATLAS Offline Software
Loading...
Searching...
No Matches
AthenaRootSharedWriterSvc.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
3*/
4
9
10#include "GaudiKernel/IAlgManager.h"
12
13#include "TBranch.h"
14#include "TClass.h"
15#include "TFile.h"
16#include "TFileMerger.h"
17#include "TKey.h"
18#include "TLeaf.h"
19#include "TMemFile.h"
20#include "TMessage.h"
21#include "TMonitor.h"
22#include "TServerSocket.h"
23#include "TSocket.h"
24#include "TString.h"
25#include "TSystem.h"
26#include "TTree.h"
27
28#include <set>
29#include <map>
30
31void* getCachedObjectAddress(TClass* cl, std::unordered_map<TClass*, void*>& cache)
32{
33 if (!cl) return nullptr;
34
35 void*& map_ptr = cache[cl];
36 if (!map_ptr) {
37 map_ptr = cl->New();
38 }
39
40 return &map_ptr;
41}
42
43/* Code from ROOT tutorials/net/parallelMergeServer.C, reduced to handle TTrees only */
44
45struct ParallelFileMerger : public TObject
46{
47 TString fFilename;
48 TFileMerger fMerger;
49 std::unordered_map<TClass*, void*> *m_cache;
50
51 ParallelFileMerger(const char *filename, std::unordered_map<TClass*, void*>* cache, int compress = ROOT::RCompressionSetting::EDefaults::kUseCompiledDefault
52 ) : fFilename(filename), fMerger(kFALSE, kTRUE), m_cache(cache)
53 {
54 fMerger.OutputFile(filename, "RECREATE", compress);
55 }
56
60
61 ULong_t Hash() const
62 {
63 return fFilename.Hash();
64 }
65
66 const char* GetName() const
67 {
68 return fFilename;
69 }
70
71// Add missing branches to client tree and BackFill before merging
72 bool syncBranches(TTree* fromTree, TTree* toTree)
73 {
74 bool updated = false;
75 const TObjArray* fromBranches = fromTree->GetListOfBranches();
76 const TObjArray* toBranches = toTree->GetListOfBranches();
77 int nBranches = fromBranches->GetEntriesFast();
78 int nEntries = toTree->GetEntries();
79 for (int k = 0; k < nBranches; ++k) {
80 TBranch* branch = static_cast<TBranch*>(fromBranches->UncheckedAt(k));
81 if (toBranches->FindObject(branch->GetName()) == nullptr) {
82 TBranch* newBranch = nullptr;
83 TClass* cl = TClass::GetClass(branch->GetClassName());
84 if (cl != nullptr) {
85 newBranch = toTree->Branch(branch->GetName(), branch->GetClassName(), nullptr, branch->GetBasketSize(), branch->GetSplitLevel());
86 void* objectAddress = getCachedObjectAddress(cl, *m_cache);
87 newBranch->SetAddress(objectAddress);
88 } else {
89 TObjArray* outLeaves = branch->GetListOfLeaves();
90 TLeaf* leaf = static_cast<TLeaf*>(outLeaves->UncheckedAt(0));
91 std::string_view attr = leaf->GetName();
92 std::string_view type = leaf->GetTypeName();
93 std::string branchSpec(attr);
94 branchSpec += '/';
95 if (type == "Int_t") branchSpec += 'I';
96 else if (type == "Short_t") branchSpec += 'S';
97 else if (type == "Long_t") branchSpec += 'L';
98 else if (type == "UInt_t") branchSpec +='i';
99 else if (type == "UShort_t") branchSpec+='s';
100 else if (type == "ULong_t") branchSpec +='l';
101 else if (type == "Float_t") branchSpec +='F';
102 else if (type == "Double_t") branchSpec+='D';
103 else if (type == "Char_t") branchSpec +='B';
104 else if (type == "UChar_t") branchSpec +='b';
105 else if (type == "Bool_t") branchSpec +='O';
106 else { branchSpec += type; }// fallback
107 newBranch = toTree->Branch(branch->GetName(), static_cast<void*>(nullptr), branchSpec.c_str(), 2048);
108 }
109 for (int m = 0; m < nEntries; ++m) {
110 newBranch->BackFill();
111 }
112 updated = true;
113 }
114 }
115 return updated;
116 }
117
118 Bool_t MergeTrees(TFile *input)
119 {
120 fMerger.AddFile(input);
121 TIter nextKey(input->GetListOfKeys());
122 while (TKey* key = static_cast<TKey*>(nextKey())) {
123 TClass* cl = TClass::GetClass(key->GetClassName());
124 if (cl != nullptr && cl->InheritsFrom("TTree")) {
125 TTree* outCollTree = static_cast<TTree*>(fMerger.GetOutputFile()->Get(key->GetName()));
126 TTree* inCollTree = static_cast<TTree*>(input->Get(key->GetName()));
127 if (inCollTree != nullptr && outCollTree != nullptr) {
128 if (syncBranches(outCollTree, inCollTree)) {
129 input->Write();
130 }
131 syncBranches(inCollTree, outCollTree);
132 }
133 }
134 }
135
136 Bool_t result = fMerger.PartialMerge(TFileMerger::kIncremental | TFileMerger::kResetable | TFileMerger::kKeepCompression);
137 nextKey = input->GetListOfKeys();
138 while (TKey* key = static_cast<TKey*>(nextKey())) {
139 TClass* cl = TClass::GetClass(key->GetClassName());
140 if (cl != nullptr && 0 != cl->GetResetAfterMerge()) {
141 key->Delete();
142 input->GetListOfKeys()->Remove(key);
143 delete key;
144 }
145 }
146 return result;
147 }
148};
149
150//___________________________________________________________________________
151AthenaRootSharedWriterSvc::AthenaRootSharedWriterSvc(const std::string& name, ISvcLocator* pSvcLocator)
152 : base_class(name, pSvcLocator)
154}
155//___________________________________________________________________________
157 ATH_MSG_INFO("in initialize()");
158
159 // Initialize IConversionSvc
160 ATH_CHECK(m_cnvSvc.retrieve());
161 IProperty* propertyServer = dynamic_cast<IProperty*>(m_cnvSvc.get());
162 if (propertyServer == nullptr) {
163 ATH_MSG_ERROR("Unable to cast conversion service to IProperty");
164 return StatusCode::FAILURE;
165 } else {
166 std::string propertyName = "ParallelCompression";
167 bool parallelCompression(false);
168 BooleanProperty parallelCompressionProp(propertyName, parallelCompression);
169 if (propertyServer->getProperty(&parallelCompressionProp).isFailure()) {
170 ATH_MSG_INFO("Conversion service does not have ParallelCompression property");
171 } else if (parallelCompressionProp.value()) {
172 propertyName = "StreamPortString";
173 std::string streamPortString("");
174 StringProperty streamPortStringProp(propertyName, streamPortString);
175 if (propertyServer->getProperty(&streamPortStringProp).isFailure()) {
176 ATH_MSG_INFO("Conversion service does not have StreamPortString property, using default TCP port: 0");
177 streamPortStringProp.setValue("?pmerge=localhost:0");
178 }
179 const std::string& pmergeProperty = streamPortStringProp.value();
180 const std::size_t eqPos = pmergeProperty.find('=');
181 if (eqPos == std::string::npos) {
182 ATH_MSG_FATAL("Malformed StreamPortString property (missing '='): " << pmergeProperty);
183 return StatusCode::FAILURE;
184 }
185 const std::string pmergePrefix = pmergeProperty.substr(0, eqPos + 1);
186 const std::string pmergeArg = pmergeProperty.substr(eqPos + 1);
187 if (pmergeArg.empty()) {
188 ATH_MSG_FATAL("Malformed StreamPortString property (empty value after '='): " << pmergeProperty);
189 return StatusCode::FAILURE;
190 }
191 std::string newStreamPortString;
192 // "?pmerge=<host>:<port>" (default) selects a TCP socket.
193 // Anything else (e.g. "?pmerge=<prefix>") is passed to gSystem->TempFileName() as a
194 // (possibly relative) prefix for a UNIX domain socket file, created as $TMPDIR/<prefix>XXXXXX.
195 if (pmergeArg.find(':') == std::string::npos) {
196 TString socketPath = pmergeArg.c_str();
197 FILE* reservedFile = gSystem->TempFileName(socketPath);
198 if (reservedFile == nullptr) {
199 ATH_MSG_FATAL("Could not create temporary file for UNIX domain socket: " << pmergeArg);
200 return StatusCode::FAILURE;
201 }
202 m_socketPath = socketPath.Data();
203 //coverity[CHECKED_RETURN]
204 std::remove(m_socketPath.c_str()); //returns 0 if ok.
205 std::fclose(reservedFile);
206 m_rootServerSocket = new TServerSocket(socketPath);
207 if (m_rootServerSocket == nullptr || !m_rootServerSocket->IsValid()) {
208 ATH_MSG_FATAL("Could not create ROOT TServerSocket (UNIX domain socket): " << m_socketPath);
209 return StatusCode::FAILURE;
210 }
211 newStreamPortString = pmergePrefix + m_socketPath;
212 ATH_MSG_DEBUG("Successfully created ROOT TServerSocket (UNIX domain socket) and added it to TMonitor: ready to accept connections, " << m_socketPath);
213 } else {
214 const std::size_t colonPos = pmergeArg.find(':');
215 int streamPort = atoi(pmergeArg.substr(colonPos + 1).c_str());
216 m_rootServerSocket = new TServerSocket(streamPort, (streamPort == 0 ? false : true), 100, -1, ESocketBindOption::kInaddrLoopback);
217 if (m_rootServerSocket == nullptr || !m_rootServerSocket->IsValid()) {
218 ATH_MSG_FATAL("Could not create ROOT TServerSocket: " << streamPort);
219 return StatusCode::FAILURE;
220 }
221 streamPort = m_rootServerSocket->GetLocalPort();
222 newStreamPortString = pmergePrefix + pmergeArg.substr(0, colonPos + 1) + std::to_string(streamPort);
223 ATH_MSG_DEBUG("Successfully created ROOT TServerSocket and added it to TMonitor: ready to accept connections, " << streamPort);
224 }
225 if (propertyServer->setProperty(propertyName,newStreamPortString).isFailure()) {
226 ATH_MSG_FATAL("Could not set Conversion Service property " << propertyName << " from " << streamPortString << " to " << newStreamPortString);
227 return StatusCode::FAILURE;
228 }
229 m_rootMonitor = new TMonitor;
231 }
232 }
233 // Count the number of output streams
234 const IAlgManager* algMgr = Gaudi::svcLocator()->as<IAlgManager>();
235 for (const auto& alg : algMgr->getAlgorithms()) {
236 if (alg->type() == "AthenaOutputStream") {
237 ATH_MSG_DEBUG("Counting " << alg->name() << " as an output stream algorithm");
239 }
240 }
241 if (m_numberOfStreams == 0) {
242 ATH_MSG_WARNING("No output stream algorithm found, setting the number of streams to 1");
244 } else {
245 ATH_MSG_INFO("Found a total of " << m_numberOfStreams << " output streams");
246 }
247
248 return StatusCode::SUCCESS;
249}
250//___________________________________________________________________________
251StatusCode AthenaRootSharedWriterSvc::share(int numClients, bool motherClient) {
252 ATH_MSG_DEBUG("Start commitOutput loop");
253 StatusCode sc = m_cnvSvc->commitOutput("", false);
254
255 // Allow ROOT clients to start up (by setting active clients)
256 // and wait to stop the ROOT server until all clients are done and metadata is written (commitOutput fail).
257 bool anyActiveClients = (m_rootServerSocket != nullptr);
258 while (sc.isSuccess() || sc.isRecoverable() || anyActiveClients) {
259 if (sc.isSuccess()) {
260 ATH_MSG_VERBOSE("Success in commitOutput loop");
261 } else if (m_rootMonitor != nullptr) {
262 TSocket* socket = m_rootMonitor->Select(1);
263 if (socket != nullptr && socket != (TSocket*)-1) {
264 ATH_MSG_DEBUG("ROOT Monitor got: " << socket);
265 if (socket->IsA() == TServerSocket::Class()) {
266 TSocket* client = (static_cast<TServerSocket*>(socket))->Accept();
267 client->Send(m_rootClientIndex, 0);
268 client->Send(1, 1);
271 if (m_rootClientCount < (numClients-1)*m_numberOfStreams + 1) {
272 m_rootMonitor->Add(client);
273 ATH_MSG_INFO("ROOT Monitor add client: " << m_rootClientIndex << ", " << client);
274 } else {
275 ATH_MSG_WARNING("ROOT Monitor do NOT add client: " << m_rootClientIndex << ", " << client);
276 client->Close("force");
278 }
279 } else {
280 TMessage* message = nullptr;
281 Int_t result = socket->Recv(message);
282 if (result < 0) {
283 ATH_MSG_ERROR("ROOT Monitor got an error while receiving the message from the socket: " << result);
284 return StatusCode::FAILURE;
285 }
286 if (message == nullptr) {
287 ATH_MSG_WARNING("ROOT Monitor got no message from socket: " << socket);
288 } else if (message->What() == kMESS_STRING) {
289 char str[64];
290 message->ReadString(str, 64);
291 ATH_MSG_INFO("ROOT Monitor client: " << socket << ", " << str);
292 m_rootMonitor->Remove(socket);
293 ATH_MSG_DEBUG("ROOT Monitor client: " << socket << ", " << socket->GetBytesRecv() << ", " << socket->GetBytesSent());
294 socket->Close();
296 if (m_rootMonitor->GetActive() == 0 || m_rootClientCount == 0) {
297 if (!motherClient) {
298 anyActiveClients = false;
299 ATH_MSG_INFO("ROOT Monitor: No more active clients...");
300 } else {
301 motherClient = false;
302 ATH_MSG_INFO("ROOT Monitor: Mother process is done...");
303 if (!m_cnvSvc->commitCatalog().isSuccess()) {
304 ATH_MSG_FATAL("Failed to commit file catalog.");
305 return StatusCode::FAILURE;
306 }
307 }
308 }
309 } else if (message->What() == kMESS_ANY) {
310 long long length;
311 TString filename;
312 int clientId;
313 message->ReadInt(clientId);
314 message->ReadTString(filename);
315 message->ReadLong64(length);
316 ATH_MSG_DEBUG("ROOT Monitor client: " << socket << ", " << clientId << ": " << filename << ", " << length);
317 std::unique_ptr<TMemFile> transient(new TMemFile(filename, message->Buffer() + message->Length(), length, "UPDATE"));
318 message->SetBufferOffset(message->Length() + length);
319 ParallelFileMerger* info = static_cast<ParallelFileMerger*>(m_rootMergers.FindObject(filename));
320 if (!info) {
321 info = new ParallelFileMerger(filename, &m_cachedObjects, transient->GetCompressionSettings());
322 m_rootMergers.Add(info);
323 ATH_MSG_INFO("ROOT Monitor ParallelFileMerger: " << info << ", for: " << filename);
324 }
325 info->MergeTrees(transient.get());
326 }
327 delete message; message = nullptr;
328 }
329 }
330 } else if (m_rootMonitor == nullptr) {
331 usleep(100);
332 }
333 // Once commitOutput failed all legacy clients are finished (writing metadata), do not call again.
334 if (sc.isSuccess() || sc.isRecoverable()) {
335 sc = m_cnvSvc->commitOutput("", false);
336 if (sc.isFailure() && !sc.isRecoverable()) {
337 ATH_MSG_INFO("commitOutput failed, metadata done.");
338 if (anyActiveClients && m_rootClientCount == 0) {
339 ATH_MSG_INFO("ROOT Monitor: No clients, terminating the loop...");
340 anyActiveClients = false;
341 }
342 }
343 }
344 }
345 ATH_MSG_INFO("End commitOutput loop");
346 return StatusCode::SUCCESS;
347}
348//___________________________________________________________________________
350 m_rootMergers.Delete();
351 return StatusCode::SUCCESS;
352}
353//___________________________________________________________________________
355 ATH_MSG_INFO("in finalize()");
356 delete m_rootMonitor; m_rootMonitor = nullptr;
357 delete m_rootServerSocket; m_rootServerSocket = nullptr;
358 if (!m_socketPath.empty()) {
359 //coverity[CHECKED_RETURN]
360 std::remove(m_socketPath.c_str()); //returns 0 on success.
361 }
362 for (auto& [cl, ptr] : m_cachedObjects) {
363 if (cl && ptr) {
364 cl->Destructor(ptr, false);
365 }
366 }
367 m_cachedObjects.clear();
368 return StatusCode::SUCCESS;
369}
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_DEBUG(x,...)
#define ATH_MSG_ERROR(x,...)
#define ATH_MSG_WARNING(x,...)
#define ATH_MSG_VERBOSE(x,...)
#define ATH_MSG_INFO(x,...)
#define ATH_MSG_FATAL(x,...)
void * getCachedObjectAddress(TClass *cl, std::unordered_map< TClass *, void * > &cache)
This file contains the class definition for the AthenaRootSharedWriterSvc class.
double length(const pvec &v)
static Double_t sc
Templated class containing a cut, name of cut and description of cut(optional) Typically,...
Definition CutFlow.h:28
virtual StatusCode stop() override
std::unordered_map< TClass *, void * > m_cachedObjects
virtual StatusCode finalize() override
virtual StatusCode share(int numClients=0, bool motherClient=false) override
virtual StatusCode initialize() override
Gaudi Service Interface method implementations:
AthenaRootSharedWriterSvc(const std::string &name, ISvcLocator *pSvcLocator)
Standard Service Constructor.
std::string m_socketPath
Path of the UNIX domain socket file, if used, removed on finalize.
ServiceHandle< AthenaPoolSharedIOCnvSvc > m_cnvSvc
T * Get(TFile &f, const std::string &n, const std::string &dir="", const chainmap_t *chainmap=0, std::vector< std::string > *saved=0)
get a histogram given a path, and an optional initial directory if histogram is not found,...
DataModel_detail::iterator< DVL > remove(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > end, const T &value)
Specialization of remove for DataVector/List.
bool syncBranches(TTree *fromTree, TTree *toTree)
ParallelFileMerger(const char *filename, std::unordered_map< TClass *, void * > *cache, int compress=ROOT::RCompressionSetting::EDefaults::kUseCompiledDefault)
std::unordered_map< TClass *, void * > * m_cache
Bool_t MergeTrees(TFile *input)