bes  Updated for version 3.19.1
BESInterface.cc
1 // BESInterface.cc
2 
3 // This file is part of bes, A C++ back-end server implementation framework
4 // for the OPeNDAP Data Access Protocol.
5 
6 // Copyright (c) 2004-2009 University Corporation for Atmospheric Research
7 // Author: Patrick West <pwest@ucar.edu> and Jose Garcia <jgarcia@ucar.edu>
8 //
9 // This library is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU Lesser General Public
11 // License as published by the Free Software Foundation; either
12 // version 2.1 of the License, or (at your option) any later version.
13 //
14 // This library is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 // Lesser General Public License for more details.
18 //
19 // You should have received a copy of the GNU Lesser General Public
20 // License along with this library; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 //
23 // You can contact University Corporation for Atmospheric Research at
24 // 3080 Center Green Drive, Boulder, CO 80301
25 
26 // (c) COPYRIGHT University Corporation for Atmospheric Research 2004-2005
27 // Please read the full copyright statement in the file COPYRIGHT_UCAR.
28 //
29 // Authors:
30 // pwest Patrick West <pwest@ucar.edu>
31 // jgarcia Jose Garcia <jgarcia@ucar.edu>
32 
33 #include "config.h"
34 
35 #include <cstdlib>
36 
37 #include <signal.h>
38 #if HAVE_UNISTD_H
39 #include <unistd.h>
40 #endif
41 
42 #include <setjmp.h> // Used for the timeout processing
43 
44 #include <string>
45 #include <sstream>
46 #include <iostream>
47 
48 #include "BESInterface.h"
49 
50 #include "TheBESKeys.h"
51 #include "BESResponseHandler.h"
52 #include "BESAggFactory.h"
53 #include "BESAggregationServer.h"
54 #include "BESReporterList.h"
55 #include "BESContextManager.h"
56 
57 #include "BESExceptionManager.h"
58 
59 #include "BESTransmitterNames.h"
60 #include "BESDataNames.h"
61 #include "BESTransmitterNames.h"
62 #include "BESReturnManager.h"
63 #include "BESSyntaxUserError.h"
64 
65 #include "BESDebug.h"
66 #include "BESStopWatch.h"
67 #include "BESTimeoutError.h"
68 #include "BESInternalError.h"
69 #include "BESInternalFatalError.h"
70 
71 #include "BESLog.h"
72 
73 using namespace std;
74 
75 static jmp_buf timeout_jump;
76 static bool timeout_jump_valid = false;
77 
78 // Define this to use sigwait() in a child thread to detect that SIGALRM
79 // has been raised (i.e., that the timeout interval has elapsed). This
80 // does not currently work, but could be a way to get information about
81 // a timeout back to the BES's client if the BES itslef were structured
82 // differently. See my comment further down. jhrg 12/28/15
83 #undef USE_SIGWAIT
84 
85 // timeout period in seconds; 0 --> no timeout. This is a static value so
86 // that it can be accessed by the signal handler. jhrg 1/4/16
87 // I've made this globally visible so that other code that might want to
88 // alter the time out value can do so and this variable can be kept consistent.
89 // See BESStreamResponseHandler::execute() for an example. jhrg 1/24/17
90 volatile int bes_timeout = 0;
91 
92 #define BES_TIMEOUT_KEY "BES.TimeOutInSeconds"
93 
94 // This function uses the static variables timeout_jump_valid and timeout_jump
95 // The code looks at the value of BES.TimeOutInSeconds and/or the timeout
96 // context sent in the current request and, if that is greater than zero,
97 // uses that as the maximum amount of time for the request. The system alarm
98 // is set and this function is registered as the handler. If timeout_jump_valid
99 // is true, then it will use longjmp() (yes, really...) to end the request. Look
100 // below in execute_request() for the call to setjump() to see how this works.
101 // See the SIGWAIT code that's commented out below for an alternative impl.
102 // jhrg 5/31/16
103 static void catch_sig_alarm(int sig)
104 {
105  if (sig == SIGALRM) {
106  LOG("BES timeout after " << bes_timeout << " seconds." << endl);
107 
108  // Causes setjmp() below to return 1; see the call to
109  // execute_data_request_plan() in execute_request() below.
110  // jhrg 12/29/15
111  if (timeout_jump_valid)
112  longjmp(timeout_jump, 1);
113  else {
114  // This is the old version of this code; it forces the BES child
115  // listener to exit without returning an error message to the
116  // OLFS/client. jhrg 12/29/15
117  signal(SIGTERM, SIG_DFL);
118  raise(SIGTERM);
119  }
120  }
121 }
122 
123 static void register_signal_handler()
124 {
125  struct sigaction act;
126  sigemptyset(&act.sa_mask);
127  sigaddset(&act.sa_mask, SIGALRM);
128  act.sa_flags = 0;
129 
130  // Note that we do not set SA_RESTART so an interrupted system call
131  // will return with an error and errno set to EINTR.
132 
133  act.sa_handler = catch_sig_alarm;
134  if (sigaction(SIGALRM, &act, 0))
135  throw BESInternalFatalError("Could not register a handler to catch alarm/timeout.", __FILE__, __LINE__);
136 }
137 
138 #if USE_SIGWAIT
139 
140 // If the BES is changed so that the plan built here is run in a child thread,
141 // then we can have a much more flexible signal catching scheme, including catching
142 // the alarm signal used for the timeout. It's not possible to throw from a child
143 // thread to a parent thread, but if the parent thread sees that SIGALRM is
144 // raised, then it can stop the child thread (which is running the 'plan') and
145 // return a suitable message to the front end. Similarly, the BES could also
146 // handle a number of other signals using this scheme. These signals (SIGPIPE, ...)
147 // are currently processed using while/for loop(s) in the bes/server code. It may
148 // be that these signals are caught only in the master listener, but I can't
149 // quite figure that out now... jhrg 12/28/15
150 //
151 // NB: It might be possible to edit this so that it writes info to the OLFS and
152 // then uses the 'raise SIGTERM' technique to exit. That way the OLFS will at least
153 // get a message about the timeout. I'm not sure how to close up the PPT part
154 // of the conversation, however. The idea would be that the current command's DHI
155 // would be passed in as an arg and then the stream accessed that way. The BESError
156 // would be written to the stream and the child process killed. jhrg 12/2/9/15
157 
158 #include <pthread.h>
159 
160 // An alternative to a function that catches the signal; use sigwait()
161 // in a child thread after marking the signal as blocked. When/if sigwait()
162 // returns, look at the signal number and if it is the alarm, sort out
163 // what to do (throw an exception, ...). NB: A signal handler cannot
164 // portably throw an exception, but this code can.
165 
166 static pthread_t alarm_thread;
167 
168 static void* alarm_wait(void * /* arg */)
169 {
170  BESDEBUG("bes", "Starting: " << __PRETTY_FUNCTION__ << endl);
171 
172  // block SIGALRM
173  sigset_t sigset;
174  sigemptyset(&sigset);
175  sigaddset(&sigset, SIGALRM);
176  sigprocmask(SIG_BLOCK, &sigset, NULL);
177 
178  // Might replace this with a while loop. Not sure about interactions
179  // with other signal processing code in the BES. jhrg 12/28/15
180  int sig;
181  int result = sigwait(&sigset, &sig);
182  if (result != 0) {
183  BESDEBUG("bes", "Fatal error establishing timeout: " << strerror(result) << endl);
184  throw BESInternalFatalError(string("Fatal error establishing timeout: ") + strerror(result), __FILE__, __LINE__);
185  }
186  else if (result == 0 && sig == SIGALRM) {
187  BESDEBUG("bes", "Timeout found in " << __PRETTY_FUNCTION__ << endl);
188  throw BESTimeoutError("Timeout", __FILE__, __LINE__);
189  }
190  else {
191  stringstream oss;
192  oss << "While waiting for a timeout, found signal '" << result << "' in " << __PRETTY_FUNCTION__ << ends;
193  BESDEBUG("bes", oss.str() << endl);
194  throw BESInternalFatalError(oss.str(), __FILE__, __LINE__);
195  }
196 }
197 
198 static void wait_for_timeout()
199 {
200  BESDEBUG("bes", "Entering: " << __PRETTY_FUNCTION__ << endl);
201 
202  pthread_attr_t thread_attr;
203 
204  if (pthread_attr_init(&thread_attr) != 0)
205  throw BESInternalFatalError("Failed to initialize pthread attributes.", __FILE__, __LINE__);
206  if (pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED /*PTHREAD_CREATE_JOINABLE*/) != 0)
207  throw BESInternalFatalError("Failed to complete pthread attribute initialization.", __FILE__, __LINE__);
208 
209  int status = pthread_create(&alarm_thread, &thread_attr, alarm_wait, NULL);
210  if (status != 0)
211  throw BESInternalFatalError("Failed to start the timeout wait thread.", __FILE__, __LINE__);
212 }
213 #endif
214 
215 BESInterface::BESInterface(ostream *output_stream) :
216  d_strm(output_stream), d_timeout_from_keys(0), d_dhi_ptr(0), d_transmitter(0)
217 {
218  if (!d_strm) {
219  throw BESInternalError("Output stream must be set in order to output responses", __FILE__, __LINE__);
220  }
221 
222  // Grab the BES Key for the timeout. Note that the Hyrax server generally
223  // overrides this value using a 'context' that is set/sent by the OLFS.
224  // Also note that a value of zero means no timeout, but that the context
225  // can override that too. jhrg 1/4/16
226  bool found;
227  string timeout_key_value;
228  TheBESKeys::TheKeys()->get_value(BES_TIMEOUT_KEY, timeout_key_value, found);
229  if (found) {
230  istringstream iss(timeout_key_value);
231  iss >> d_timeout_from_keys;
232  }
233 
234  // Install signal handler for alarm() here
235  register_signal_handler();
236 
237 #if USE_SIGWAIT
238  wait_for_timeout();
239 #endif
240 }
241 
242 #if 0
243 extern BESStopWatch *bes_timing::elapsedTimeToReadStart;
244 extern BESStopWatch *bes_timing::elapsedTimeToTransmitStart;
245 #endif
246 
285 int BESInterface::execute_request(const string &from)
286 {
287  BESDEBUG("bes", "Entering: " << __PRETTY_FUNCTION__ << endl);
288 
289  if (!d_dhi_ptr) {
290  throw BESInternalError("DataHandlerInterface can not be null", __FILE__, __LINE__);
291  }
292 
293  BESStopWatch sw;
294  if (BESISDEBUG(TIMING_LOG)) {
295  // It would be great to have more info to put here, but that is buried in
296  // BESXMLInterface::build_data_request_plan() where the XML document is
297  // parsed. jhrg 11/9/17
298  sw.start("BESInterface::execute_request", d_dhi_ptr->data[REQUEST_ID]);
299 #if 0
300  bes_timing::elapsedTimeToReadStart = new BESStopWatch();
301  bes_timing::elapsedTimeToReadStart->start("TIME_TO_READ_START", d_dhi_ptr->data[REQUEST_ID]);
302 
303  bes_timing::elapsedTimeToTransmitStart = new BESStopWatch();
304  bes_timing::elapsedTimeToTransmitStart->start("TIME_TO_TRANSMIT_START", d_dhi_ptr->data[REQUEST_ID]);
305 #endif
306  }
307 
308  // TODO These never change for the life of a BES, so maybe they can move out of
309  // code that runs for every request? jhrg 11/8/17
310  d_dhi_ptr->set_output_stream(d_strm);
311  d_dhi_ptr->data[REQUEST_FROM] = from;
312 
313  // TODO If this is only used for logging, it is not needed since the log has a copy
314  // of the BES PID. jhrg 11/13/17
315  ostringstream ss;
316  ss << getpid();
317  d_dhi_ptr->data[SERVER_PID] = ss.str();
318 
319  // We split up the calls for the reason that if we catch an
320  // exception during the initialization, building, execution, or response
321  // transmit of the request then we can transmit the exception/error
322  // information.
323  //
324  // TODO status is not used. jhrg 11/9/17
325  int status = 0; // save the return status from exception_manager() and return that.
326  try {
327  VERBOSE(/*d_dhi_ptr->data[SERVER_PID] << " from " <<*/ d_dhi_ptr->data[REQUEST_FROM] << " request received" << endl);
328 
329  // Initialize the transmitter for this interface instance to the BASIC
330  // TRANSMITTER. This ensures that a simple response, such as an error,
331  // can be sent back to the OLFS should that be needed.
332  d_transmitter = BESReturnManager::TheManager()->find_transmitter(BASIC_TRANSMITTER);
333  if (!d_transmitter)
334  throw BESInternalError(string("Unable to find transmitter '") + BASIC_TRANSMITTER + "'", __FILE__, __LINE__);
335 
336  build_data_request_plan();
337 
338  // This method does two key things: Calls the request handler to make a
339  // 'response object' (the C++ object that will hold the response) and
340  // then calls the transmitter to actually send it or build and send it.
341  //
342  // The timeout is also set in execute_data_request_plan(). The alarm signal
343  // handler (above), run when the timeout expires, will call longjmp with a
344  // return value of 1.
345  if (setjmp(timeout_jump) == 0) {
346  timeout_jump_valid = true;
347 
348  // Set timeout? Use either the value from the keys or a context
349  bool found = false;
350  string context = BESContextManager::TheManager()->get_context("bes_timeout", found);
351  if (found) {
352  bes_timeout = strtol(context.c_str(), NULL, 10);
353  VERBOSE(d_dhi_ptr->data[REQUEST_FROM] << "Set request timeout to " << bes_timeout << " seconds (from context)." << endl);
354  alarm(bes_timeout);
355  }
356  else if (d_timeout_from_keys != 0) {
357  bes_timeout = d_timeout_from_keys;
358  VERBOSE(d_dhi_ptr->data[REQUEST_FROM] << "Set request timeout to " << bes_timeout << " seconds (from keys)." << endl);
359  alarm(bes_timeout);
360  }
361 
362  execute_data_request_plan();
363 
364  // Only clear the timeout if it has been set.
365  if (bes_timeout != 0) {
366  bes_timeout = 0;
367  alarm(0);
368  }
369 
370  // Once we exit the block where setjmp() was called, the jump_buf is not valid
371  timeout_jump_valid = false;
372  }
373  else {
374  ostringstream oss;
375  oss << "BES listener timeout after " << bes_timeout << " seconds." << ends;
376  throw BESTimeoutError(oss.str(), __FILE__, __LINE__);
377  }
378 
379  d_dhi_ptr->executed = true;
380  }
381  catch (BESError & ex) {
382  timeout_jump_valid = false;
383  return exception_manager(ex);
384  }
385  catch (bad_alloc &e) {
386  timeout_jump_valid = false;
387  BESInternalFatalError ex(string("BES out of memory: ") + e.what(), __FILE__, __LINE__);
388  return exception_manager(ex);
389  }
390  catch (exception &e) {
391  timeout_jump_valid = false;
392  BESInternalFatalError ex(string("C++ Exception: ") + e.what(), __FILE__, __LINE__);
393  return exception_manager(ex);
394  }
395  catch (...) {
396  timeout_jump_valid = false;
397  BESInternalError ex("An undefined exception has been thrown", __FILE__, __LINE__);
398  return exception_manager(ex);
399  }
400 
401 #if 0
402  delete bes_timing::elapsedTimeToReadStart;
403  bes_timing::elapsedTimeToReadStart = 0;
404 
405  delete bes_timing::elapsedTimeToTransmitStart;
406  bes_timing::elapsedTimeToTransmitStart = 0;
407 
408 #endif
409 
410  finish();
411 
412  return 0; //jhrg 11/9/17 TODO return status;
413 }
414 
415 // I think this code was written when execute_request() called transmit_data()
416 // (and invoke_aggregation()). I think that the code up to the log_status()
417 // call is redundant. This means that so is the param 'status'. jhrg 12/23/15
418 void BESInterface::finish()
419 {
420 #if 1
421  // This seems really odd to me... writing to cout and erasing the errors.
422  // jhrg 11/9/17
423  //
424  // If there is error information then the transmit of the error failed,
425  // print it to standard out. Once printed, delete the error
426  // information since we are done with it.
427  if (d_dhi_ptr->error_info) {
428  d_dhi_ptr->error_info->print(cout);
429  delete d_dhi_ptr->error_info;
430  d_dhi_ptr->error_info = 0;
431  }
432 #endif
433  // if there is a problem with the rest of these steps then all we will
434  // do is log it to the BES log file and not handle the exception with
435  // the exception manager.
436  try {
437  log_status();
438  end_request();
439  }
440  catch (BESError &ex) {
441  LOG("Problem logging status or running end of request cleanup: " << ex.get_message() << endl);
442  }
443  catch (...) {
444  LOG("Unknown problem logging status or running end of request cleanup" << endl);
445  }
446 }
447 
448 int BESInterface::finish_with_error(int status)
449 {
450  if (!d_dhi_ptr->error_info) {
451  // there wasn't an error ... so now what?
452  BESInternalError ex("Finish_with_error called with no error object", __FILE__, __LINE__);
453  status = exception_manager(ex);
454  }
455 
456  finish();
457  return status;
458 }
459 
466 {
467  // now clean up any containers that were used in the request, release
468  // the resource
470  while (d_dhi_ptr->container) {
471  d_dhi_ptr->container->release();
473  }
474 }
475 
489 {
490  return BESExceptionManager::TheEHM()->handle_exception(e, *d_dhi_ptr);
491 }
492 
501 void BESInterface::dump(ostream & strm) const
502 {
503  strm << BESIndent::LMarg << "BESInterface::dump - (" << (void *) this << ")" << endl;
504  BESIndent::Indent();
505 
506  strm << BESIndent::LMarg << "data handler interface:" << endl;
507  BESIndent::Indent();
508  d_dhi_ptr->dump(strm);
509  BESIndent::UnIndent();
510 
511  if (d_transmitter) {
512  strm << BESIndent::LMarg << "transmitter:" << endl;
513  BESIndent::Indent();
514  d_transmitter->dump(strm);
515  BESIndent::UnIndent();
516  }
517  else {
518  strm << BESIndent::LMarg << "transmitter: not set" << endl;
519  }
520 
521  BESIndent::UnIndent();
522 }
virtual void dump(ostream &strm) const
dumps information about this object
error thrown if there is a user syntax error in the request or any other user error
exception thrown if an internal error is found and is fatal to the BES
exception thrown if inernal error encountered
void next_container()
set the container pointer to the next * container in the list, null if at the end or no containers in...
virtual void dump(std::ostream &strm) const
dumps information about this object
virtual std::string get_message()
get the error message for this exception
Definition: BESError.h:97
BESDataHandlerInterface * d_dhi_ptr
Allocated by the child class.
Definition: BESInterface.h:121
virtual int exception_manager(BESError &e)
Manage any exceptions thrown during the whole process.
BESTransmitter * d_transmitter
The Transmitter to use for the result.
Definition: BESInterface.h:122
virtual string get_context(const string &name, bool &found)
retrieve the value of the specified context from the BES
void get_value(const string &s, string &val, bool &found)
Retrieve the value of a given key, if set.
Definition: TheBESKeys.cc:422
virtual bool start(string name)
Definition: BESStopWatch.cc:57
Abstract exception class for the BES with basic string message.
Definition: BESError.h:56
static TheBESKeys * TheKeys()
Definition: TheBESKeys.cc:62
void dump(ostream &strm) const
dumps information about this object
map< string, string > data
the map of string data that will be required for the current request.
virtual void end_request()
End the BES request.
void first_container()
set the container pointer to the first container in the containers list
BESInfo * error_info
error information object
virtual int handle_exception(BESError &e, BESDataHandlerInterface &dhi)
Manage any exceptions thrown during the handling of a request.
virtual int execute_request(const string &from)
The entry point for command execution; called by BESServerHandler::execute()
virtual void print(ostream &strm)
print the information from this informational object to the specified stream
Definition: BESInfo.cc:249
BESContainer * container
pointer to current container in this interface