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
73using 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
88volatile int bes_timeout = 0;
89#endif
90
91#define BES_TIMEOUT_KEY "BES.TimeOutInSeconds"
92
93static inline void downcase(string &s)
94{
95 transform(s.begin(), s.end(), s.begin(), [](int c) { return std::toupper(c); });
96}
97
102ostream &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
115static 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
182static pthread_t alarm_thread;
183
184static 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
214static 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
231BESInterface::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
376int 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
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
477int 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;
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
526void 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.