OFstreamCollator.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration |
5  \\ / A nd | www.openfoam.com
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8  Copyright (C) 2017-2018 OpenFOAM Foundation
9  Copyright (C) 2019-2025 OpenCFD Ltd.
10 -------------------------------------------------------------------------------
11 License
12  This file is part of OpenFOAM.
13 
14  OpenFOAM is free software: you can redistribute it and/or modify it
15  under the terms of the GNU General Public License as published by
16  the Free Software Foundation, either version 3 of the License, or
17  (at your option) any later version.
18 
19  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
20  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22  for more details.
23 
24  You should have received a copy of the GNU General Public License
25  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
26 
27 \*---------------------------------------------------------------------------*/
28 
29 #include "OFstreamCollator.H"
30 #include "OFstream.H"
31 #include "decomposedBlockData.H"
32 #include "dictionary.H"
34 
35 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
36 
37 namespace Foam
38 {
39  defineTypeNameAndDebug(OFstreamCollator, 0);
40 }
41 
42 
43 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
44 
45 bool Foam::OFstreamCollator::writeFile
46 (
47  const label comm,
48  const word& objectType,
49  const fileName& fName,
50  const string& masterData,
51  const labelUList& recvSizes,
52  const UPtrList<SubList<char>>& slaveData, // optional slave data
53  IOstreamOption streamOpt,
54  IOstreamOption::atomicType atomic,
55  IOstreamOption::appendType append,
56  const dictionary& headerEntries
57 )
58 {
59  if (debug)
60  {
61  Pout<< "OFstreamCollator : Writing master " << label(masterData.size())
62  << " bytes to " << fName << " using comm " << comm
63  << " and " << slaveData.size() << " sub-ranks" << endl;
64 
65  forAll(slaveData, proci)
66  {
67  if (slaveData.set(proci))
68  {
69  Pout<< " " << proci
70  << " size:" << slaveData[proci].size()
71  << endl;
72  }
73  }
74  }
75 
76  autoPtr<OSstream> osPtr;
77  if (UPstream::master(comm))
78  {
79  Foam::mkDir(fName.path());
80  osPtr.reset(new OFstream(atomic, fName, streamOpt, append));
81  auto& os = *osPtr;
82 
84  {
85  // No IOobject so cannot use IOobject::writeHeader
86 
87  // FoamFile
89  (
90  os,
91  streamOpt, // streamOpt for container
92  objectType,
93  "", // note
94  "", // location (leave empty instead inaccurate)
95  fName.name(), // object name
96  headerEntries
97  );
98  }
99  }
100 
101  // Assuming threaded writing hides any slowness so we
102  // can use scheduled communication to send the data to
103  // the master processor in order. However can be unstable
104  // for some mpi so default is non-blocking.
105  const UPstream::commsTypes myCommsType
106  (
107  (
108  fileOperations::masterUncollatedFileOperation::
109  maxMasterFileBufferSize == 0
110  )
113  );
114 
115 
116  UList<char> slice
117  (
118  const_cast<char*>(masterData.data()),
119  label(masterData.size())
120  );
121 
122  List<std::streamoff> blockOffset;
124  (
125  comm,
126  osPtr,
127  blockOffset,
128  slice,
129  recvSizes,
130  slaveData,
131  myCommsType,
132  false // do not reduce return state
133  );
134 
135  if (osPtr && !osPtr->good())
136  {
137  FatalIOErrorInFunction(*osPtr)
138  << "Failed writing to " << fName << exit(FatalIOError);
139  }
140 
141  if (debug)
142  {
143  Pout<< "OFstreamCollator : Finished writing " << masterData.size()
144  << " bytes";
145  if (UPstream::master(comm))
146  {
147  off_t sum = 0;
148  for (const label recv : recvSizes)
149  {
150  sum += recv;
151  }
152  // Use std::to_string to display long int
153  Pout<< " (overall " << std::to_string(sum) << ')';
154  }
155  Pout<< " to " << fName
156  << " using comm " << comm << endl;
157  }
158 
159  return true;
160 }
161 
162 
163 void* Foam::OFstreamCollator::writeAll(void *threadarg)
164 {
165  OFstreamCollator& handler = *static_cast<OFstreamCollator*>(threadarg);
166 
167  // Consume stack
168  while (true)
169  {
170  writeData* ptr = nullptr;
171 
172  {
173  std::lock_guard<std::mutex> guard(handler.mutex_);
174  if (handler.objects_.size())
175  {
176  ptr = handler.objects_.pop();
177  }
178  }
179 
180  if (!ptr)
181  {
182  break;
183  }
184  else
185  {
186  // Convert storage to pointers
187  PtrList<SubList<char>> slaveData;
188  if (ptr->slaveData_.size())
189  {
190  slaveData.resize(ptr->slaveData_.size());
191  forAll(slaveData, proci)
192  {
193  if (ptr->slaveData_.set(proci))
194  {
195  slaveData.set
196  (
197  proci,
198  new SubList<char>
199  (
200  ptr->slaveData_[proci],
201  ptr->sizes_[proci]
202  )
203  );
204  }
205  }
206  }
207 
208  bool ok = writeFile
209  (
210  ptr->comm_,
211  ptr->objectType_,
212  ptr->pathName_,
213  ptr->data_,
214  ptr->sizes_,
215  slaveData,
216  ptr->streamOpt_,
217  ptr->atomic_,
218  ptr->append_,
219  ptr->headerEntries_
220  );
221  if (!ok)
222  {
223  FatalIOErrorInFunction(ptr->pathName_)
224  << "Failed writing " << ptr->pathName_
225  << exit(FatalIOError);
226  }
227 
228  delete ptr;
229  }
230  //sleep(1);
231  }
232 
233  if (debug)
234  {
235  Pout<< "OFstreamCollator : Exiting write thread " << endl;
236  }
237 
238  {
239  std::lock_guard<std::mutex> guard(handler.mutex_);
240  handler.threadRunning_ = false;
241  }
242 
243  return nullptr;
244 }
245 
246 
247 void Foam::OFstreamCollator::waitForBufferSpace(const off_t wantedSize) const
248 {
249  while (true)
250  {
251  // Count files to be written
252  off_t totalSize = 0;
253 
254  {
255  std::lock_guard<std::mutex> guard(mutex_);
256  forAllConstIters(objects_, iter)
257  {
258  totalSize += iter()->size();
259  }
260  }
261 
262  if
263  (
264  totalSize == 0
265  || (wantedSize >= 0 && (totalSize+wantedSize) <= maxBufferSize_)
266  )
267  {
268  break;
269  }
270 
271  if (debug)
272  {
273  std::lock_guard<std::mutex> guard(mutex_);
274  Pout<< "OFstreamCollator : Waiting for buffer space."
275  << " Currently in use:" << totalSize
276  << " limit:" << maxBufferSize_
277  << " files:" << objects_.size()
278  << endl;
279  }
280 
281  sleep(5);
282  }
283 }
284 
285 
286 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
287 
288 Foam::OFstreamCollator::OFstreamCollator(const off_t maxBufferSize)
289 :
290  maxBufferSize_(maxBufferSize),
291  threadRunning_(false),
292  localComm_(UPstream::worldComm),
293  threadComm_(UPstream::dupCommunicator(localComm_))
294 {}
295 
296 
298 (
299  const off_t maxBufferSize,
300  const label comm
301 )
302 :
303  maxBufferSize_(maxBufferSize),
304  threadRunning_(false),
305  localComm_(comm),
306  threadComm_(UPstream::dupCommunicator(localComm_))
307 {}
308 
309 
310 // * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
311 
313 {
314  if (thread_)
315  {
316  if (debug)
317  {
318  Pout<< "~OFstreamCollator : Waiting for write thread" << endl;
319  }
320  thread_->join();
321  thread_.reset(nullptr);
322  }
323 
325 }
326 
327 
328 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
329 
331 (
332  const word& objectType,
333  const fileName& fName,
334  const string& data,
335  IOstreamOption streamOpt,
338  const bool useThread,
339  const dictionary& headerEntries
340 )
341 {
342  // Determine (on master) sizes to receive. Note: do NOT use thread
343  // communicator
344  labelList recvSizes;
345  decomposedBlockData::gather(localComm_, label(data.size()), recvSizes);
346 
347  off_t totalSize = 0;
348  label maxLocalSize = 0;
349  {
350  if (UPstream::master(localComm_))
351  {
352  for (const label recvSize : recvSizes)
353  {
354  totalSize += recvSize;
355  maxLocalSize = max(maxLocalSize, recvSize);
356  }
357  }
358  Pstream::broadcasts(localComm_, totalSize, maxLocalSize);
359  }
360 
361  if (!useThread || maxBufferSize_ == 0 || maxLocalSize > maxBufferSize_)
362  {
363  if (debug)
364  {
365  Pout<< "OFstreamCollator : non-thread gather and write of " << fName
366  << " using local comm " << localComm_ << endl;
367  }
368  // Direct collating and writing (so master blocks until all written!)
369  const PtrList<SubList<char>> dummySlaveData;
370  return writeFile
371  (
372  localComm_,
373  objectType,
374  fName,
375  data,
376  recvSizes,
377  dummySlaveData,
378  streamOpt,
379  atomic,
380  append,
381  headerEntries
382  );
383  }
384  else if (totalSize <= maxBufferSize_)
385  {
386  // Total size can be stored locally so receive all data now and only
387  // do the writing in the thread
388 
389  if (debug)
390  {
391  Pout<< "OFstreamCollator : non-thread gather; thread write of "
392  << fName << endl;
393  }
394 
395  if (Pstream::master(localComm_))
396  {
397  waitForBufferSpace(totalSize);
398  }
399 
400 
401  // Receive in chunks of labelMax (2^31-1) since this is the maximum
402  // size that a List can be
403 
404  autoPtr<writeData> fileAndDataPtr
405  (
406  new writeData
407  (
408  threadComm_, // Note: comm not actually used anymore
409  objectType,
410  fName,
411  (
412  Pstream::master(localComm_)
413  ? data // Only used on master
414  : string::null
415  ),
416  recvSizes,
417  streamOpt,
418  atomic,
419  append,
420  headerEntries
421  )
422  );
423  writeData& fileAndData = fileAndDataPtr();
424 
425  PtrList<List<char>>& slaveData = fileAndData.slaveData_;
426 
427  UList<char> slice(const_cast<char*>(data.data()), label(data.size()));
428 
429  slaveData.setSize(recvSizes.size());
430 
431  // Gather all data onto master. Is done in local communicator since
432  // not in write thread. Note that we do not store in contiguous
433  // buffer since that would limit to 2G chars.
434  const label startOfRequests = UPstream::nRequests();
435  if (Pstream::master(localComm_))
436  {
437  for (label proci = 1; proci < slaveData.size(); proci++)
438  {
439  slaveData.set(proci, new List<char>(recvSizes[proci]));
441  (
443  proci,
444  slaveData[proci].data(),
445  slaveData[proci].size_bytes(),
447  localComm_
448  );
449  }
450  }
451  else
452  {
453  if
454  (
456  (
458  0,
459  slice.cdata(),
460  slice.size_bytes(),
462  localComm_
463  )
464  )
465  {
467  << "Cannot send outgoing message. "
468  << "to:" << 0 << " nBytes:"
469  << label(slice.size_bytes())
471  }
472  }
473  UPstream::waitRequests(startOfRequests);
474 
475  {
476  std::lock_guard<std::mutex> guard(mutex_);
477 
478  // Append to thread buffer
479  objects_.push(fileAndDataPtr.ptr());
480 
481  // Start thread if not running
482  if (!threadRunning_)
483  {
484  if (thread_)
485  {
486  if (debug)
487  {
488  Pout<< "OFstreamCollator : Waiting for write thread"
489  << endl;
490  }
491  thread_->join();
492  }
493 
494  if (debug)
495  {
496  Pout<< "OFstreamCollator : Starting write thread"
497  << endl;
498  }
499  thread_.reset(new std::thread(writeAll, this));
500  threadRunning_ = true;
501  }
502  }
503 
504  return true;
505  }
506  else
507  {
508  if (debug)
509  {
510  Pout<< "OFstreamCollator : thread gather and write of " << fName
511  << " using communicator " << threadComm_ << endl;
512  }
513 
514  if (!UPstream::haveThreads())
515  {
517  << "mpi does not seem to have thread support."
518  << " Make sure to set buffer size 'maxThreadFileBufferSize'"
519  << " to at least " << totalSize
520  << " to be able to do the collating before threading."
521  << exit(FatalError);
522  }
523 
524  if (Pstream::master(localComm_))
525  {
526  waitForBufferSpace(data.size());
527  }
528 
529  {
530  std::lock_guard<std::mutex> guard(mutex_);
531 
532  // Push all file info on buffer. Note that no slave data provided
533  // so it will trigger communication inside the thread
534  objects_.push
535  (
536  new writeData
537  (
538  threadComm_,
539  objectType,
540  fName,
541  data,
542  recvSizes,
543  streamOpt,
544  atomic,
545  append,
546  headerEntries
547  )
548  );
549 
550  if (!threadRunning_)
551  {
552  if (thread_)
553  {
554  if (debug)
555  {
556  Pout<< "OFstreamCollator : Waiting for write thread"
557  << endl;
558  }
559  thread_->join();
560  }
561 
562  if (debug)
563  {
564  Pout<< "OFstreamCollator : Starting write thread" << endl;
565  }
566  thread_.reset(new std::thread(writeAll, this));
567  threadRunning_ = true;
568  }
569  }
570 
571  return true;
572  }
573 }
574 
575 
577 {
578  // Wait for all buffer space to be available i.e. wait for all jobs
579  // to finish
580  if (Pstream::master(localComm_))
581  {
582  if (debug)
583  {
584  Pout<< "OFstreamCollator : waiting for thread to have consumed all"
585  << endl;
586  }
587  waitForBufferSpace(-1);
588  }
589 }
590 
591 
592 // ************************************************************************* //
void size(const label n)
Older name for setAddressableSize.
Definition: UList.H:114
A class for handling file names.
Definition: fileName.H:72
virtual ~OFstreamCollator()
Destructor.
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:125
static bool writeBlocks(const label comm, autoPtr< OSstream > &osPtr, List< std::streamoff > &blockOffset, const UList< char > &masterData, const labelUList &recvSizes, const UPtrList< SubList< char >> &slaveData, const UPstream::commsTypes commsType, const bool syncReturnState=true)
Write *this. Ostream only valid on master.
static void writeData(Ostream &os, const Type &val)
commsTypes
Communications types.
Definition: UPstream.H:77
error FatalError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL ERROR&#39; header text and sta...
A list of keyword definitions, which are a keyword followed by a number of values (eg...
Definition: dictionary.H:129
static void broadcasts(const int communicator, Type &value, Args &&... values)
Broadcast multiple items to all communicator ranks. Does nothing in non-parallel. ...
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:600
static void freeCommunicator(const label communicator, const bool withComponents=true)
Free a previously allocated communicator.
Definition: UPstream.C:622
label max(const labelHashSet &set, label maxValue=labelMin)
Find the max value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:40
static label nRequests() noexcept
Number of outstanding requests (on the internal list of requests)
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:529
static int & msgType() noexcept
Message tag of standard messages.
Definition: UPstream.H:1787
A simple container for options an IOstream can normally have.
static std::streamsize read(const UPstream::commsTypes commsType, const int fromProcNo, Type *buffer, std::streamsize count, const int tag=UPstream::msgType(), const int communicator=UPstream::worldComm, UPstream::Request *req=nullptr)
Receive buffer contents (contiguous types) from given processor.
dimensioned< Type > sum(const DimensionedField< Type, GeoMesh > &f1, const label comm)
static void waitRequests()
Wait for all requests to finish.
Definition: UPstream.H:2189
UList< label > labelUList
A UList of labels.
Definition: UList.H:76
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:286
atomicType
Atomic operations (output)
bool write(const word &objectType, const fileName &, const string &data, IOstreamOption streamOpt, IOstreamOption::atomicType atomic, IOstreamOption::appendType append, const bool useThread=true, const dictionary &headerEntries=dictionary::null)
Write file with contents.
bool mkDir(const fileName &pathName, mode_t mode=0777)
Make a directory and return an error if it could not be created.
Definition: POSIX.C:616
"scheduled" (MPI standard) : (MPI_Send, MPI_Recv)
A class for handling words, derived from Foam::string.
Definition: word.H:63
void waitAll()
Wait for all thread actions to have finished.
static bool write(const UPstream::commsTypes commsType, const int toProcNo, const Type *buffer, std::streamsize count, const int tag=UPstream::msgType(), const int communicator=UPstream::worldComm, UPstream::Request *req=nullptr, const UPstream::sendModes sendMode=UPstream::sendModes::normal)
Write buffer contents (contiguous types only) to given processor.
errorManip< error > abort(error &err)
Definition: errorManip.H:139
no append (truncates existing)
unsigned int sleep(const unsigned int sec)
Sleep for the specified number of seconds.
Definition: POSIX.C:1549
static const string null
An empty string.
Definition: string.H:202
int debug
Static debugging option.
OBJstream os(runTime.globalPath()/outputName)
defineTypeNameAndDebug(combustionModel, 0)
appendType
File appending (NO_APPEND | APPEND_APP | APPEND_ATE)
OFstreamCollator(const off_t maxBufferSize)
Construct from buffer size. 0 = do not use thread.
rAUs append(new volScalarField(IOobject::groupName("rAU", phase1.name()), 1.0/(U1Eqn.A()+byDt(max(phase1.residualAlpha() - alpha1, scalar(0)) *rho1))))
#define FatalIOErrorInFunction(ios)
Report an error message using Foam::FatalIOError.
Definition: error.H:629
static bool master(const label communicator=worldComm)
True if process corresponds to the master rank in the communicator.
Definition: UPstream.H:1619
"nonBlocking" (immediate) : (MPI_Isend, MPI_Irecv)
static bool haveThreads() noexcept
Have support for threads.
Definition: UPstream.H:1591
prefixOSstream Pout
OSstream wrapped stdout (std::cout) with parallel prefix.
Inter-processor communications stream.
Definition: UPstream.H:65
static void writeHeader(Ostream &os, IOstreamOption streamOptContainer, const word &objectType, const string &note, const fileName &location, const word &objectName, const dictionary &extraEntries)
Helper: write FoamFile IOobject header.
Namespace for OpenFOAM.
forAllConstIters(mixture.phases(), phase)
Definition: pEqn.H:28
static void gather(const label comm, const label data, labelList &datas)
Helper: gather single label. Note: using native Pstream.
IOerror FatalIOError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL IO ERROR&#39; header text and ...