Teuchos Package Browser (Single Doxygen Collection)  Version of the Day
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
Teuchos_MpiReductionOpSetter.cpp
Go to the documentation of this file.
1 // @HEADER
2 // *****************************************************************************
3 // Teuchos: Common Tools Package
4 //
5 // Copyright 2004 NTESS and the Teuchos contributors.
6 // SPDX-License-Identifier: BSD-3-Clause
7 // *****************************************************************************
8 // @HEADER
9 
11 
12 #ifdef HAVE_MPI
13 # ifdef MPIAPI
14 # define CALL_API MPIAPI
15 # else
16 # define CALL_API
17 # endif
18 
19 //
20 // mfh 23 Nov 2014: My commits over the past day or two attempt to
21 // address Bug 6263. In particular, the code as I found it had the
22 // following issues:
23 //
24 // 1. Static RCP instances (that persist past return of main())
25 // 2. Static MPI_Op datum (that persists past MPI_Finalize())
26 // 3. Code won't work with MPI_THREAD_{SERIALIZED,MULTIPLE},
27 // because it assumes that only one MPI_Op for reductions
28 // is needed at any one time
29 //
30 // I'm neglecting Issue #3 for now and focusing on the first two
31 // issues. #1 goes away if one doesn't use RCPs and handles
32 // deallocation manually (we could also use std::shared_ptr, but that
33 // would require C++11). #2 goes away with the standard idiom of an
34 // MPI_Finalize() hook (attach a (key,value) pair to MPI_COMM_SELF).
35 //
36 
37 extern "C" {
38 
39 // The MPI_Op that implements the reduction or scan operation will
40 // call this function. We only need to create the MPI_Op once
41 // (lazily, on demand). This function in turn will invoke
42 // theReductOp_ (see below), which gets set to the current reduction
43 // operation. Thus, we only never need to create one MPI_Op, but we
44 // swap out the function. This is meant to save overhead in creating
45 // and freeing MPI_Op for each reduction or scan.
46 void CALL_API
47 Teuchos_MPI_reduction_op (void* invec, void* inoutvec,
48  int* len, MPI_Datatype* datatype);
49 } // extern "C"
50 
51 namespace { // anonymous
52 
53 //
54 // theMpiOp_: The MPI_Op singleton that implements the Teuchos
55 // reduction or scan operation. We only need to create the MPI_Op
56 // once (lazily, on demand). When we create the MPI_Op, we stash its
57 // "destructor" in MPI_COMM_SELF so that it gets freed at
58 // MPI_Finalize. (This is a standard MPI idiom.)
59 //
60 // This variable is global, persistent (until MPI_Finalize is called),
61 // and initialized lazily.
62 MPI_Op theMpiOp_ = MPI_OP_NULL;
63 
64 // The current reduction or scan "function." (It's actually a class
65 // instance.)
66 //
67 // This static variable is _NOT_ persistent. It does not need
68 // deallocation.
69 const Teuchos::Details::MpiReductionOpBase* theReductOp_ = NULL;
70 
71 // Free the given MPI_Op, and return the error code returned by MPI_Op_free.
72 int
73 freeMpiOp (MPI_Op* op)
74 {
75  // If this function is called as an MPI_Finalize hook, MPI should
76  // still be initialized at this point, and it should be OK to call
77  // MPI functions. Thus, we don't need to check if MPI is
78  // initialized.
79  int err = MPI_SUCCESS;
80  if (op != NULL) {
81  err = MPI_Op_free (op);
82  if (err == MPI_SUCCESS) {
83  // No externally visible side effects unless the above function succeeded.
84  *op = MPI_OP_NULL;
85  }
86  }
87  return err;
88 }
89 
90 // Free the MPI_Op singleton (theMpiOp_), and return the error code
91 // returned by freeMpiOp(). As a side effect, if freeing succeeds,
92 // set theMpiOp_ to MPI_OP_NULL.
93 //
94 // This is the singleton's "destructor" that we attach to
95 // MPI_COMM_SELF as an MPI_Finalize hook.
96 int
97 freeMpiOpCallback (MPI_Comm, int, void*, void*)
98 {
99  // We don't need any of the arguments to this function, since we're
100  // just freeing the singleton.
101  if (theMpiOp_ == MPI_OP_NULL) {
102  return MPI_SUCCESS;
103  } else {
104  return freeMpiOp (&theMpiOp_);
105  }
106 }
107 
108 // Create the MPI_Op singleton that invokes the
109 // Teuchos_MPI_reduction_op callback. Assign the MPI_Op to theMpiOp_,
110 // and set it up with an MPI_Finalize hook so it gets freed
111 // automatically.
112 void createReductOp ()
113 {
114 #if MPI_VERSION >= 2
115 
116  // This function has side effects on the global singleton theMpiOp_.
117  // This check ensures that the function is idempotent. We only need
118  // to create the MPI_Op singleton once.
119  if (theMpiOp_ != MPI_OP_NULL) {
120  return; // We've already called this function; we don't have to again.
121  }
122 
123  MPI_Op mpi_op = MPI_OP_NULL;
124 
125  // FIXME (mfh 23 Nov 2014) I found the following comment here:
126  // "Assume op is commutative". That's what it means to pass 1 as
127  // the second argument. I don't know whether it's a good idea to
128  // keep that assumption.
129  int err = MPI_Op_create (&Teuchos_MPI_reduction_op, 1, &mpi_op);
131  err != MPI_SUCCESS, std::runtime_error, "Teuchos::createReductOp: "
132  "MPI_Op_create (for custom reduction operator) failed!");
133 
134  // Use the standard MPI idiom (attach a (key,value) pair to
135  // MPI_COMM_SELF with a "destructor" function) in order that
136  // theMpiOp_ gets freed at MPI_Finalize, if necessary.
137 
138  // 'key' is an output argument of MPI_Comm_create_keyval.
139  int key = MPI_KEYVAL_INVALID;
140  err = MPI_Comm_create_keyval (MPI_COMM_NULL_COPY_FN, freeMpiOpCallback,
141  &key, NULL);
142  if (err != MPI_SUCCESS) {
143  // Attempt to clean up by freeing the newly created MPI_Op. If
144  // cleaning up fails, just let it slide, since we're already in
145  // trouble if MPI can't create a (key,value) pair.
146  (void) MPI_Op_free (&mpi_op);
148  true, std::runtime_error, "Teuchos::createReductOp: "
149  "MPI_Comm_create_keyval (for custom reduction operator) failed!");
150  }
151  int val = key; // doesn't matter
152 
153  // Attach the attribute to MPI_COMM_SELF.
154  err = MPI_Comm_set_attr (MPI_COMM_SELF, key, &val);
155  if (err != MPI_SUCCESS) {
156  // MPI (versions up to and including 3.0) doesn't promise correct
157  // behavior after any function returns something other than
158  // MPI_SUCCESS. Thus, it's not required to try to free the new
159  // key via MPI_Comm_free_keyval. Furthermore, if something went
160  // wrong with MPI_Comm_set_attr, it's likely that the attribute
161  // mechanism is broken. Thus, it would be unwise to call
162  // MPI_Comm_free_keyval.
163  //
164  // I optimistically assume that the "rest" of MPI is still
165  // working, and attempt to clean up by freeing the newly created
166  // MPI_Op. If cleaning up fails, just let it slide, since we're
167  // already in trouble if MPI can't create a (key,value) pair.
168  (void) MPI_Op_free (&mpi_op);
170  true, std::runtime_error, "Teuchos::createReductOp: "
171  "MPI_Comm_set_attr (for custom reduction operator) failed!");
172  }
173 
174  // It looks weird to "free" the key right away. However, this does
175  // not actually cause the "destructor" to be called. It only gets
176  // called at MPI_FINALIZE. See MPI 3.0 standard, Section 6.7.2,
177  // MPI_COMM_FREE_KEYVAL:
178  //
179  // "Note that it is not erroneous to free an attribute key that is
180  // in use, because the actual free does not transpire until after
181  // all references (in other communicators on the process) to the key
182  // have been freed. These references need to be explicitly freed by
183  // the program, either via calls to MPI_COMM_DELETE_ATTR that free
184  // one attribute instance, or by calls to MPI_COMM_FREE that free
185  // all attribute instances associated with the freed communicator."
186  //
187  // We rely here on the latter mechanism. MPI_FINALIZE calls
188  // MPI_COMM_FREE on MPI_COMM_SELF, so we do not need to call it
189  // explicitly.
190  //
191  // It's not clear what to do if the MPI_* calls above succeeded, but
192  // this call fails (i.e., returns != MPI_SUCCESS). We could throw;
193  // this would make sense to do, because MPI (versions up to and
194  // including 3.0) doesn't promise correct behavior after any MPI
195  // function returns something other than MPI_SUCCESS. We could also
196  // be optimistic and just ignore the return value, hoping that if
197  // the above calls succeeded, then the communicator will get freed
198  // at MPI_FINALIZE, even though the unfreed key may leak memory (see
199  // Bug 6338). I've chosen the latter.
200  (void) MPI_Comm_free_keyval (&key);
201 
202  // The "transaction" succeeded; save the result.
203  theMpiOp_ = mpi_op;
204 
205 #else // MPI_VERSION < 2
206 # error "Sorry, you need an MPI implementation that supports at least MPI 2.0 in order to build this code. MPI 2.0 came out in 1997. I wrote this comment in 2017. If you really _really_ want MPI 1.x support, please file a GitHub issue for this feature request at github.com/trilinos/trilinos/issues with an expression of its priority and we will get to it as soon as we can."
207 #endif // MPI_VERSION >= 2
208 }
209 
210 void
211 setReductOp (const Teuchos::Details::MpiReductionOpBase* reductOp)
212 {
213  if (theMpiOp_ == MPI_OP_NULL) {
214  createReductOp ();
215  }
216  theReductOp_ = reductOp;
217 }
218 
219 } // namespace (anonymous)
220 
221 extern "C" {
222 
223 void CALL_API
224 Teuchos_MPI_reduction_op (void* invec,
225  void* inoutvec,
226  int* len,
227  MPI_Datatype* datatype)
228 {
229  if (theReductOp_ != NULL) {
230  theReductOp_->reduce (invec, inoutvec, len, datatype);
231  }
232 }
233 
234 } // extern "C"
235 
236 namespace Teuchos {
237 namespace Details {
238 
239 MPI_Op setMpiReductionOp (const MpiReductionOpBase& reductOp)
240 {
241  setReductOp (&reductOp);
243  (theMpiOp_ == MPI_OP_NULL, std::logic_error, "Teuchos::Details::"
244  "setMpiReductionOp: Failed to create reduction MPI_Op theMpiOp_. "
245  "This should never happen. "
246  "Please report this bug to the Teuchos developers.");
247  return theMpiOp_;
248 }
249 
250 } // namespace Details
251 } // namespace Teuchos
252 
253 #endif // HAVE_MPI
#define TEUCHOS_TEST_FOR_EXCEPTION(throw_exception_test, Exception, msg)
Macro for throwing an exception with breakpointing to ease debugging.
Implementation detail of Teuchos&#39; MPI wrapper.