bes  Updated for version 3.20.13
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 #if HAVE_UNISTD_H
38 #include <unistd.h>
39 #endif
40 
41 #include <string>
42 #include <sstream>
43 #include <future> // std::async, std::future
44 #include <chrono> // std::chrono::milliseconds
45 #include <algorithm>
46 
47 #include "BESInterface.h"
48 
49 #include "TheBESKeys.h"
50 #include "BESContextManager.h"
51 
52 #include "BESTransmitterNames.h"
53 #include "BESDataNames.h"
54 #include "BESReturnManager.h"
55 
56 #include "BESInfoList.h"
57 #include "BESXMLInfo.h"
58 
59 #include "BESUtil.h"
60 #include "BESDebug.h"
61 #include "BESStopWatch.h"
62 #include "BESInternalError.h"
63 #include "BESInternalFatalError.h"
64 #include "ServerAdministrator.h"
65 #include "RequestServiceTimer.h"
66 
67 #include "BESLog.h"
68 
69 // If not defined, this is false (source code file names are logged). jhrg 10/4/18
70 #define EXCLUDE_FILE_INFO_FROM_LOG "BES.DoNotLogSourceFilenames"
71 #define prolog std::string("BESInterface::").append(__func__).append("() - ")
72 
73 using namespace std;
74 
75 // Define this to use sigwait() in a child thread to detect that SIGALRM
76 // has been raised (i.e., that the timeout interval has elapsed). This
77 // does not currently work, but could be a way to get information about
78 // a timeout back to the BES's client if the BES itself were structured
79 // differently. See my comment further down. jhrg 12/28/15
80 #define USE_SIGWAIT 0
81 
82 #if 0
83 // timeout period in seconds; 0 --> no timeout. This is a global value so
84 // that it can be accessed by the signal handler. jhrg 1/4/16
85 // I've made this globally visible so that other code that might want to
86 // alter the timeout value can do so and this variable can be kept consistent.
87 // See BESStreamResponseHandler::execute() for an example. jhrg 1/24/17
88 volatile int bes_timeout = 0;
89 #endif
90 
91 #define BES_TIMEOUT_KEY "BES.TimeOutInSeconds"
92 
93 static inline void downcase(string &s)
94 {
95  transform(s.begin(), s.end(), s.begin(), [](int c) { return std::toupper(c); });
96 }
97 
102 ostream &add_memory_info(ostream &out)
103 {
104  long mem_size = BESUtil::get_current_memory_usage();
105  if (mem_size) {
106  out << ", current memory usage is " << mem_size << " KB.";
107  }
108  else {
109  out << ", current memory usage is unknown.";
110  }
111 
112  return out;
113 }
114 
115 static void log_error(const BESError &e)
116 {
117  string error_name;
118 
119  switch (e.get_bes_error_type()) {
120  case BES_INTERNAL_FATAL_ERROR:
121  error_name = "BES Internal Fatal Error";
122  break;
123 
124  case BES_INTERNAL_ERROR:
125  error_name = "BES Internal Error";
126  break;
127 
128  case BES_SYNTAX_USER_ERROR:
129  error_name = "BES User Syntax Error";
130  break;
131 
132  case BES_FORBIDDEN_ERROR:
133  error_name = "BES Forbidden Error";
134  break;
135 
136  case BES_NOT_FOUND_ERROR:
137  error_name = "BES Not Found Error";
138  break;
139 
140  default:
141  error_name = "BES Error";
142  break;
143  }
144 
145  if (TheBESKeys::TheKeys()->read_bool_key(EXCLUDE_FILE_INFO_FROM_LOG, false)) {
146  ERROR_LOG("ERROR: " << error_name << ": " << e.get_message() << add_memory_info << endl);
147  }
148  else {
149  ERROR_LOG("ERROR: " << error_name << ": " << e.get_message()
150  << " (" << e.get_file() << ":" << e.get_line() << ")"
151  << add_memory_info << endl);
152  }
153 }
154 
155 #if USE_SIGWAIT
156 // If the BES is changed so that the plan built here is run in a child thread,
157 // then we can have a much more flexible signal catching scheme, including catching
158 // the alarm signal used for the timeout. It's not possible to throw from a child
159 // thread to a parent thread, but if the parent thread sees that SIGALRM is
160 // raised, then it can stop the child thread (which is running the 'plan') and
161 // return a suitable message to the front end. Similarly, the BES could also
162 // handle a number of other signals using this scheme. These signals (SIGPIPE, ...)
163 // are currently processed using while/for loop(s) in the bes/server code. It may
164 // be that these signals are caught only in the master listener, but I can't
165 // quite figure that out now... jhrg 12/28/15
166 //
167 // NB: It might be possible to edit this so that it writes info to the OLFS and
168 // then uses the 'raise SIGTERM' technique to exit. That way the OLFS will at least
169 // get a message about the timeout. I'm not sure how to close up the PPT part
170 // of the conversation, however. The idea would be that the current command's DHI
171 // would be passed in as an arg and then the stream accessed that way. The BESError
172 // would be written to the stream and the child process killed. jhrg 12/2/9/15
173 
174 #include <pthread.h>
175 
176 // An alternative to a function that catches the signal; use sigwait()
177 // in a child thread after marking the signal as blocked. When/if sigwait()
178 // returns, look at the signal number and if it is the alarm, sort out
179 // what to do (throw an exception, ...). NB: A signal handler cannot
180 // portably throw an exception, but this code can.
181 
182 static pthread_t alarm_thread;
183 
184 static void* alarm_wait(void * /* arg */)
185 {
186  BESDEBUG("bes", "Starting: " << __PRETTY_FUNCTION__ << endl);
187 
188  // block SIGALRM
189  sigset_t sigset;
190  sigemptyset(&sigset);
191  sigaddset(&sigset, SIGALRM);
192  sigprocmask(SIG_BLOCK, &sigset, NULL);
193 
194  // Might replace this with a while loop. Not sure about interactions
195  // with other signal processing code in the BES. jhrg 12/28/15
196  int sig;
197  int result = sigwait(&sigset, &sig);
198  if (result != 0) {
199  BESDEBUG("bes", "Fatal error establishing timeout: " << strerror(result) << endl);
200  throw BESInternalFatalError(string("Fatal error establishing timeout: ") + strerror(result), __FILE__, __LINE__);
201  }
202  else if (result == 0 && sig == SIGALRM) {
203  BESDEBUG("bes", "Timeout found in " << __PRETTY_FUNCTION__ << endl);
204  throw BESTimeoutError("Timeout", __FILE__, __LINE__);
205  }
206  else {
207  stringstream oss;
208  oss << "While waiting for a timeout, found signal '" << result << "' in " << __PRETTY_FUNCTION__ << ends;
209  BESDEBUG("bes", oss.str() << endl);
210  throw BESInternalFatalError(oss.str(), __FILE__, __LINE__);
211  }
212 }
213 
214 static void wait_for_timeout()
215 {
216  BESDEBUG("bes", "Entering: " << __PRETTY_FUNCTION__ << endl);
217 
218  pthread_attr_t thread_attr;
219 
220  if (pthread_attr_init(&thread_attr) != 0)
221  throw BESInternalFatalError("Failed to initialize pthread attributes.", __FILE__, __LINE__);
222  if (pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED /*PTHREAD_CREATE_JOINABLE*/) != 0)
223  throw BESInternalFatalError("Failed to complete pthread attribute initialization.", __FILE__, __LINE__);
224 
225  int status = pthread_create(&alarm_thread, &thread_attr, alarm_wait, NULL);
226  if (status != 0)
227  throw BESInternalFatalError("Failed to start the timeout wait thread.", __FILE__, __LINE__);
228 }
229 #endif
230 
231 BESInterface::BESInterface(ostream *output_stream) :
232  d_strm(output_stream)
233 {
234  if (!d_strm) {
235  throw BESInternalError("Output stream must be set in order to output responses", __FILE__, __LINE__);
236  }
237 
238 #if 0
239  // Grab the BES Key for the timeout. Note that the Hyrax server generally
240  // overrides this value using a 'context' that is set/sent by the OLFS.
241  // Also note that a value of zero means no timeout, but that the context
242  // can override that too. jhrg 1/4/16
243  d_timeout_from_keys = TheBESKeys::TheKeys()->read_int_key(BES_TIMEOUT_KEY, 0);
244 #endif
245 #if 0
246  bool found;
247  string timeout_key_value;
248  TheBESKeys::TheKeys()->get_value(BES_TIMEOUT_KEY, timeout_key_value, found);
249  if (found) {
250  istringstream iss(timeout_key_value);
251  iss >> d_timeout_from_keys;
252  }
253 #endif
254 }
255 
267 {
268  bool found = false;
269  string context = BESContextManager::TheManager()->get_context("errors", found);
270  downcase(context);
271  if (found && context == XML_ERRORS)
272  dhi.error_info = new BESXMLInfo();
273  else
274  dhi.error_info = BESInfoList::TheList()->build_info();
275 
276  log_error(e);
277 
278  string admin_email;
279  try {
281  admin_email = sd.get_email();
282  }
283  catch (...) {
284  admin_email = "support@opendap.org";
285  }
286  if (admin_email.empty()) {
287  admin_email = "support@opendap.org";
288  }
289 
290  dhi.error_info->begin_response(dhi.action_name.empty() ? "BES" : dhi.action_name, dhi);
291 
292  dhi.error_info->add_exception(e, admin_email);
293 
294  dhi.error_info->end_response();
295 
296  return (int)e.get_bes_error_type();
297 }
298 
305 {
306  // Set timeout? Use either the value from the keys or a context
307  bool found = false;
308  string context = BESContextManager::TheManager()->get_context("bes_timeout", found);
309  if (found) {
310  d_bes_timeout = strtol(context.c_str(), NULL, 10);
311  VERBOSE(d_dhi_ptr->data[REQUEST_FROM] << "Set request timeout to " << d_bes_timeout << " seconds (from context)." << endl);
312 
313  }
314  else {
315  // Grab the BES Key for the timeout. Note that the Hyrax server generally
316  // overrides this value using a 'context' that is set/sent by the OLFS.
317  // Also note that a value of zero means no timeout, but that the context
318  // can override that too. jhrg 1/4/16
319  //
320  // If the value is not set in teh BES keys, d_timeout_from_keys will get the
321  // default value of 0. jhrg 4/20/22
322  d_bes_timeout = TheBESKeys::TheKeys()->read_int_key(BES_TIMEOUT_KEY, 0);
323  VERBOSE(d_dhi_ptr->data[REQUEST_FROM] << "Set request timeout to " << d_bes_timeout << " seconds (from keys)." << endl);
324  }
325 }
326 
331 {
332  d_bes_timeout = 0;
333 
334  // Clearing bes_timeout requires disabling the timeout in RequestServiceTimer::TheTimer()
336 }
337 
376 int BESInterface::execute_request(const string &from)
377 {
378  BESDEBUG("bes", "Entering: " << __PRETTY_FUNCTION__ << endl);
379 
380  if (!d_dhi_ptr) {
381  throw BESInternalError("DataHandlerInterface can not be null", __FILE__, __LINE__);
382  }
383 
384  BESStopWatch sw;
385  if (BESDebug::IsSet(TIMING_LOG_KEY)) {
386  // It would be great to have more info to put here, but that is buried in
387  // BESXMLInterface::build_data_request_plan() where the XML document is
388  // parsed. jhrg 11/9/17
389  sw.start("BESInterface::execute_request", d_dhi_ptr->data[REQUEST_ID]);
390  }
391 
392  // TODO These never change for the life of a BES, so maybe they can move out of
393  // code that runs for every request? jhrg 11/8/17
394  d_dhi_ptr->set_output_stream(d_strm);
395  d_dhi_ptr->data[REQUEST_FROM] = from;
396 
397  // TODO If this is only used for logging, it is not needed since the log has a copy
398  // of the BES PID. jhrg 11/13/17
399  ostringstream ss;
400  ss << getpid();
401  d_dhi_ptr->data[SERVER_PID] = ss.str();
402 
403  // We split up the calls for the reason that if we catch an
404  // exception during the initialization, building, execution, or response
405  // transmit of the request then we can transmit the exception/error
406  // information.
407  int status = 0; // save the return status from exception_manager() and return that.
408  try {
409  VERBOSE(d_dhi_ptr->data[REQUEST_FROM] << " request received" << endl);
410 
411  // Initialize the transmitter for this interface instance to the BASIC
412  // TRANSMITTER. This ensures that a simple response, such as an error,
413  // can be sent back to the OLFS should that be needed.
414  d_transmitter = BESReturnManager::TheManager()->find_transmitter(BASIC_TRANSMITTER);
415  if (!d_transmitter)
416  throw BESInternalError(string("Unable to find transmitter '") + BASIC_TRANSMITTER + "'", __FILE__, __LINE__);
417 
418  build_data_request_plan();
419 
420  set_bes_timeout();
421 
422  // Start the request service timer. The value bes_timeout == 0 disables the timeout,
423  // otherwise the timeout can be disabled by BESUtil::conditional_timeout_cancel()
424  // used by the transmitters, transforms to disable request timeout as streaming begins.
425  RequestServiceTimer::TheTimer()->start(std::chrono::seconds{d_bes_timeout});
426  BESDEBUG("request_timer",prolog << RequestServiceTimer::TheTimer()->dump() << endl);
427 
428  // This method (execute_data_request_plan()) does two key things:
429  // Calls the request handler to make a response object' (the C++
430  // object that will hold the response) and then calls the transmitter
431  // to actually send it or build and send it.
432 
433  // HK-474. The exception caused by the errant config file in the ticket is
434  // thrown from inside SaxParserWrapper::rethrowException(). It will be caught
435  // below. jhrg 11/12//19
436  execute_data_request_plan();
437 
438  // clear the timeout
440 
441  d_dhi_ptr->executed = true;
442  }
443  catch (const BESError &e) {
444  BESDEBUG("bes", string(__PRETTY_FUNCTION__) + " - Caught BESError. msg: " << e.get_message() << endl );
445  status = handleException(e, *d_dhi_ptr);
446  }
447  catch (const bad_alloc &e) {
448  stringstream msg;
449  msg << __PRETTY_FUNCTION__ << " - BES out of memory. msg: " << e.what() << endl;
450  BESDEBUG("bes", msg.str() << endl );
451  BESInternalFatalError ex(msg.str(), __FILE__, __LINE__);
452  status = handleException(ex, *d_dhi_ptr);
453  }
454  catch (const exception &e) {
455  stringstream msg;
456  msg << __PRETTY_FUNCTION__ << " - Caught C++ Exception. msg: " << e.what() << endl;
457  BESDEBUG("bes", msg.str() << endl );
458  BESInternalError ex(msg.str(), __FILE__, __LINE__);
459  status = handleException(ex, *d_dhi_ptr);
460  }
461  catch (...) {
462  string msg = string(__PRETTY_FUNCTION__) + " - An unidentified exception has been thrown.";
463  BESDEBUG("bes", msg << endl );
464  BESInternalError ex(msg, __FILE__, __LINE__);
465  status = handleException(ex, *d_dhi_ptr);
466  }
467 
468  return status;
469 }
470 
477 int BESInterface::finish(int status)
478 {
479  if (d_dhi_ptr->error_info) {
480  d_dhi_ptr->error_info->print(*d_strm /*cout*/);
481  delete d_dhi_ptr->error_info;
482  d_dhi_ptr->error_info = 0;
483  }
484 
485  // if there is a problem with the rest of these steps then all we will
486  // do is log it to the BES log file and not handle the exception with
487  // the exception manager.
488  try {
489  log_status();
490  end_request();
491  }
492  catch (BESError &ex) {
493  ERROR_LOG("Problem logging status or running end of request cleanup: " << ex.get_message() << endl);
494  }
495  catch (...) {
496  ERROR_LOG("Unknown problem logging status or running end of request cleanup" << endl);
497  }
498 
499  return status;
500 }
501 
508 {
509  // now clean up any containers that were used in the request, release
510  // the resource
512  while (d_dhi_ptr->container) {
513  d_dhi_ptr->container->release();
515  }
516 }
517 
526 void BESInterface::dump(ostream & strm) const
527 {
528  strm << BESIndent::LMarg << "BESInterface::dump - (" << (void *) this << ")" << endl;
529  BESIndent::Indent();
530 
531  strm << BESIndent::LMarg << "data handler interface:" << endl;
532  BESIndent::Indent();
533  d_dhi_ptr->dump(strm);
534  BESIndent::UnIndent();
535 
536  if (d_transmitter) {
537  strm << BESIndent::LMarg << "transmitter:" << endl;
538  BESIndent::Indent();
539  d_transmitter->dump(strm);
540  BESIndent::UnIndent();
541  }
542  else {
543  strm << BESIndent::LMarg << "transmitter: not set" << endl;
544  }
545 
546  BESIndent::UnIndent();
547 }
548 
virtual std::string get_context(const std::string &name, bool &found)
retrieve the value of the specified context from the BES
Structure storing information used by the BES to handle the request.
std::map< std::string, std::string > data
the map of string data that will be required for the current request.
void dump(std::ostream &strm) const override
dumps information about this object
void first_container()
set the container pointer to the first container in the containers list
BESContainer * container
pointer to current container in this interface
BESInfo * error_info
error information object
void next_container()
set the container pointer to the next * container in the list, null if at the end or no containers in...
static bool IsSet(const std::string &flagName)
see if the debug context flagName is set to true
Definition: BESDebug.h:168
Base exception class for the BES with basic string message.
Definition: BESError.h:59
unsigned int get_line() const
get the line number where the exception was thrown
Definition: BESError.h:129
unsigned int get_bes_error_type() const
Return the return code for this error class.
Definition: BESError.h:157
const char * what() const noexcept override
Return a brief message about the exception.
Definition: BESError.h:168
std::string get_file() const
get the file name where the exception was thrown
Definition: BESError.h:120
std::string get_message() const
get the error message for this exception
Definition: BESError.h:111
virtual void begin_response(const std::string &response_name, BESDataHandlerInterface &dhi)
begin the informational response
Definition: BESInfo.cc:124
virtual void print(std::ostream &strm)
print the information from this informational object to the specified stream
Definition: BESInfo.cc:261
virtual void add_exception(const BESError &e, const std::string &admin)
add exception information to this informational object
Definition: BESInfo.cc:234
static int handleException(const BESError &e, BESDataHandlerInterface &dhi)
Make a BESXMLInfo object to hold the error information.
virtual int finish(int status)
virtual int execute_request(const std::string &from)
The entry point for command execution; called by BESServerHandler::execute()
virtual void end_request()
End the BES request.
BESDataHandlerInterface * d_dhi_ptr
Allocated by the child class.
Definition: BESInterface.h:124
BESTransmitter * d_transmitter
The Transmitter to use for the result.
Definition: BESInterface.h:125
void clear_bes_timeout()
Clear the bes timeout.
void dump(std::ostream &strm) const override
dumps information about this object
void set_bes_timeout()
Set the int 'd_bes_timeout' Use either the value of a 'bes_timeout' context or the value set in the B...
exception thrown if internal error encountered
exception thrown if an internal error is found and is fatal to the BES
virtual bool start(std::string name)
Definition: BESStopWatch.cc:67
error thrown if there is a user syntax error in the request or any other user error
virtual void dump(std::ostream &strm) const
dumps information about this object
static long get_current_memory_usage() noexcept
Get the Resident Set Size in KB.
Definition: BESUtil.cc:86
represents an xml formatted response object
Definition: BESXMLInfo.h:48
static RequestServiceTimer * TheTimer()
Return a pointer to a singleton timer instance. If an instance does not exist it will create and init...
void start(std::chrono::milliseconds timeout_ms)
Set/Reset the timer start_time to now().
void disable_timeout()
Set the time_out is disabled.
void get_value(const std::string &s, std::string &val, bool &found)
Retrieve the value of a given key, if set.
Definition: TheBESKeys.cc:340
static TheBESKeys * TheKeys()
Definition: TheBESKeys.cc:71
int read_int_key(const std::string &key, int default_value)
Read an integer-valued key from the bes.conf file.
Definition: TheBESKeys.cc:450
A ServerAdministrator object from the TheBESKeys associated with the string SERVER_ADMIN_KEY.