bes Updated for version 3.21.1
The Backend Server (BES) is the lower two tiers of the Hyrax data server
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
102std::string memory_info()
103{
104 long mem_size = BESUtil::get_current_memory_usage();
105 string mem_info;
106 if (mem_size) {
107 mem_info = "Current memory usage is: " + std::to_string(mem_size) +" KB.";
108 }
109 else {
110 mem_info = "Current memory usage is unknown.";
111 }
112
113 return mem_info;
114}
115
116
117static void log_error(const BESError &e)
118{
119 string err_msg(e.get_message());
120
121 if (TheBESKeys::read_bool_key(EXCLUDE_FILE_INFO_FROM_LOG, false)) {
122 ERROR_LOG("ERROR! " + e.error_name() + ": " + BESUtil::remove_crlf(err_msg) + " " + memory_info());
123 }
124 else {
125 ERROR_LOG("ERROR! " + e.error_name() + ": " + BESUtil::remove_crlf(err_msg)
126 + " (" + e.get_file() + ":" + std::to_string(e.get_line()) + ") "
127 + memory_info() + "\n");
128 }
129}
130
131#if USE_SIGWAIT
132// If the BES is changed so that the plan built here is run in a child thread,
133// then we can have a much more flexible signal catching scheme, including catching
134// the alarm signal used for the timeout. It's not possible to throw from a child
135// thread to a parent thread, but if the parent thread sees that SIGALRM is
136// raised, then it can stop the child thread (which is running the 'plan') and
137// return a suitable message to the front end. Similarly, the BES could also
138// handle a number of other signals using this scheme. These signals (SIGPIPE, ...)
139// are currently processed using while/for loop(s) in the bes/server code. It may
140// be that these signals are caught only in the master listener, but I can't
141// quite figure that out now... jhrg 12/28/15
142//
143// NB: It might be possible to edit this so that it writes info to the OLFS and
144// then uses the 'raise SIGTERM' technique to exit. That way the OLFS will at least
145// get a message about the timeout. I'm not sure how to close up the PPT part
146// of the conversation, however. The idea would be that the current command's DHI
147// would be passed in as an arg and then the stream accessed that way. The BESError
148// would be written to the stream and the child process killed. jhrg 12/2/9/15
149
150#include <pthread.h>
151
152// An alternative to a function that catches the signal; use sigwait()
153// in a child thread after marking the signal as blocked. When/if sigwait()
154// returns, look at the signal number and if it is the alarm, sort out
155// what to do (throw an exception, ...). NB: A signal handler cannot
156// portably throw an exception, but this code can.
157
158static pthread_t alarm_thread;
159
160static void* alarm_wait(void * /* arg */)
161{
162 BESDEBUG("bes", "Starting: " << __PRETTY_FUNCTION__ << endl);
163
164 // block SIGALRM
165 sigset_t sigset;
166 sigemptyset(&sigset);
167 sigaddset(&sigset, SIGALRM);
168 sigprocmask(SIG_BLOCK, &sigset, NULL);
169
170 // Might replace this with a while loop. Not sure about interactions
171 // with other signal processing code in the BES. jhrg 12/28/15
172 int sig;
173 int result = sigwait(&sigset, &sig);
174 if (result != 0) {
175 BESDEBUG("bes", "Fatal error establishing timeout: " << strerror(result) << endl);
176 throw BESInternalFatalError(string("Fatal error establishing timeout: ") + strerror(result), __FILE__, __LINE__);
177 }
178 else if (result == 0 && sig == SIGALRM) {
179 BESDEBUG("bes", "Timeout found in " << __PRETTY_FUNCTION__ << endl);
180 throw BESTimeoutError("Timeout", __FILE__, __LINE__);
181 }
182 else {
183 stringstream oss;
184 oss << "While waiting for a timeout, found signal '" << result << "' in " << __PRETTY_FUNCTION__ << ends;
185 BESDEBUG("bes", oss.str() << endl);
186 throw BESInternalFatalError(oss.str(), __FILE__, __LINE__);
187 }
188}
189
190static void wait_for_timeout()
191{
192 BESDEBUG("bes", "Entering: " << __PRETTY_FUNCTION__ << endl);
193
194 pthread_attr_t thread_attr;
195
196 if (pthread_attr_init(&thread_attr) != 0)
197 throw BESInternalFatalError("Failed to initialize pthread attributes.", __FILE__, __LINE__);
198 if (pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED /*PTHREAD_CREATE_JOINABLE*/) != 0)
199 throw BESInternalFatalError("Failed to complete pthread attribute initialization.", __FILE__, __LINE__);
200
201 int status = pthread_create(&alarm_thread, &thread_attr, alarm_wait, NULL);
202 if (status != 0)
203 throw BESInternalFatalError("Failed to start the timeout wait thread.", __FILE__, __LINE__);
204}
205#endif
206
207BESInterface::BESInterface(ostream *output_stream) :
208 d_strm(output_stream)
209{
210 if (!d_strm) {
211 throw BESInternalError("Output stream must be set in order to output responses", __FILE__, __LINE__);
212 }
213
214#if 0
215 // Grab the BES Key for the timeout. Note that the Hyrax server generally
216 // overrides this value using a 'context' that is set/sent by the OLFS.
217 // Also note that a value of zero means no timeout, but that the context
218 // can override that too. jhrg 1/4/16
219 d_timeout_from_keys = TheBESKeys::TheKeys()->read_int_key(BES_TIMEOUT_KEY, 0);
220#endif
221#if 0
222 bool found;
223 string timeout_key_value;
224 TheBESKeys::TheKeys()->get_value(BES_TIMEOUT_KEY, timeout_key_value, found);
225 if (found) {
226 istringstream iss(timeout_key_value);
227 iss >> d_timeout_from_keys;
228 }
229#endif
230}
231
243{
244 bool found = false;
245 string context = BESContextManager::TheManager()->get_context("errors", found);
246 downcase(context);
247 if (found && context == XML_ERRORS)
248 dhi.error_info = new BESXMLInfo();
249 else
250 dhi.error_info = BESInfoList::TheList()->build_info();
251
252 log_error(e);
253
254 string admin_email;
255 try {
257 admin_email = sd.get_email();
258 }
259 catch (...) {
260 admin_email = "support@opendap.org";
261 }
262 if (admin_email.empty()) {
263 admin_email = "support@opendap.org";
264 }
265
266 dhi.error_info->begin_response(dhi.action_name.empty() ? "BES" : dhi.action_name, dhi);
267
268 dhi.error_info->add_exception(e, admin_email);
269
270 dhi.error_info->end_response();
271
272 return (int)e.get_bes_error_type();
273}
274
281{
282 // Set timeout? Use either the value from the keys or a context
283 bool found = false;
284 string context = BESContextManager::TheManager()->get_context("bes_timeout", found);
285 if (found) {
286 d_bes_timeout = strtol(context.c_str(), NULL, 10);
287 VERBOSE(d_dhi_ptr->data[REQUEST_FROM] + "Set request timeout to " + std::to_string(d_bes_timeout)
288 + " seconds (from context).");
289
290 }
291 else {
292 // Grab the BES Key for the timeout. Note that the Hyrax server generally
293 // overrides this value using a 'context' that is set/sent by the OLFS.
294 // Also note that a value of zero means no timeout, but that the context
295 // can override that too. jhrg 1/4/16
296 //
297 // If the value is not set in teh BES keys, d_timeout_from_keys will get the
298 // default value of 0. jhrg 4/20/22
299 d_bes_timeout = TheBESKeys::TheKeys()->read_int_key(BES_TIMEOUT_KEY, 0);
300 VERBOSE(d_dhi_ptr->data[REQUEST_FROM] + "Set request timeout to " + std::to_string(d_bes_timeout)
301 + " seconds (from keys).");
302 }
303}
304
309{
310 d_bes_timeout = 0;
311
312 // Clearing bes_timeout requires disabling the timeout in RequestServiceTimer::TheTimer()
314}
315
354int BESInterface::execute_request(const string &from)
355{
356 BESDEBUG("bes", "Entering: " << __PRETTY_FUNCTION__ << endl);
357
358 if (!d_dhi_ptr) {
359 throw BESInternalError("DataHandlerInterface can not be null", __FILE__, __LINE__);
360 }
361
362#if 0
363 // At this point we have not begun processing the request, so there is no request id in the BESDataHandlerInterface
364 // and the previous request id is still cached in BESLog. If we start this timer here then the previous request
365 // id will be used and that makes things very confusing in the timing log.
366 // Sorting this out seems like a lot of work with little benefit, so I am disabling this call
367 // for the time being - ndp 03/07/2025
368 BES_COMMAND_TIMING(prolog, d_dhi_ptr);
369#endif
370
371 // TODO These never change for the life of a BES, so maybe they can move out of
372 // code that runs for every request? jhrg 11/8/17
373 d_dhi_ptr->set_output_stream(d_strm);
374 d_dhi_ptr->data[REQUEST_FROM] = from;
375
376 // TODO If this is only used for logging, it is not needed since the log has a copy
377 // of the BES PID. jhrg 11/13/17
378 d_dhi_ptr->data[SERVER_PID] = to_string(getpid());
379
380
381 // We split up the calls for the reason that if we catch an
382 // exception during the initialization, building, execution, or response
383 // transmit of the request then we can transmit the exception/error
384 // information.
385 int status = 0; // save the return status from exception_manager() and return that.
386 try {
387 VERBOSE(d_dhi_ptr->data[REQUEST_FROM] + " request received");
388
389 // Initialize the transmitter for this interface instance to the BASIC
390 // TRANSMITTER. This ensures that a simple response, such as an error,
391 // can be sent back to the OLFS should that be needed.
392 d_transmitter = BESReturnManager::TheManager()->find_transmitter(BASIC_TRANSMITTER);
393 if (!d_transmitter)
394 throw BESInternalError(string("Unable to find transmitter '") + BASIC_TRANSMITTER + "'", __FILE__, __LINE__);
395
396 build_data_request_plan();
397
399
400 // Start the request service timer. The value bes_timeout == 0 disables the timeout,
401 // otherwise the timeout can be disabled by BESUtil::conditional_timeout_cancel()
402 // used by the transmitters, transforms to disable request timeout as streaming begins.
403 RequestServiceTimer::TheTimer()->start(std::chrono::seconds{d_bes_timeout});
404 BESDEBUG("request_timer",prolog << RequestServiceTimer::TheTimer()->dump() << endl);
405
406 // This method (execute_data_request_plan()) does two key things:
407 // Calls the request handler to make a response object' (the C++
408 // object that will hold the response) and then calls the transmitter
409 // to actually send it or build and send it.
410
411 // HK-474. The exception caused by the errant config file in the ticket is
412 // thrown from inside SaxParserWrapper::rethrowException(). It will be caught
413 // below. jhrg 11/12//19
414 execute_data_request_plan();
415
416 // clear the timeout
418
419 d_dhi_ptr->executed = true;
420 }
421 catch (const BESError &e) {
422 BESDEBUG("bes", string(__PRETTY_FUNCTION__) + " - Caught BESError. msg: " << e.get_message() << endl );
423 status = handleException(e, *d_dhi_ptr);
424 }
425 catch (const bad_alloc &e) {
426 stringstream msg;
427 msg << __PRETTY_FUNCTION__ << " - BES out of memory. msg: " << e.what() << endl;
428 BESDEBUG("bes", msg.str() << endl );
429 BESInternalFatalError ex(msg.str(), __FILE__, __LINE__);
430 status = handleException(ex, *d_dhi_ptr);
431 }
432 catch (const exception &e) {
433 stringstream msg;
434 msg << __PRETTY_FUNCTION__ << " - Caught C++ Exception. msg: " << e.what() << endl;
435 BESDEBUG("bes", msg.str() << endl );
436 BESInternalError ex(msg.str(), __FILE__, __LINE__);
437 status = handleException(ex, *d_dhi_ptr);
438 }
439 catch (...) {
440 string msg = string(__PRETTY_FUNCTION__) + " - An unidentified exception has been thrown.";
441 BESDEBUG("bes", msg << endl );
442 BESInternalError ex(msg, __FILE__, __LINE__);
443 status = handleException(ex, *d_dhi_ptr);
444 }
445
446 return status;
447}
448
455int BESInterface::finish(int status)
456{
457 if (d_dhi_ptr->error_info) {
458 d_dhi_ptr->error_info->print(*d_strm /*cout*/);
459 delete d_dhi_ptr->error_info;
460 d_dhi_ptr->error_info = 0;
461 }
462
463 // if there is a problem with the rest of these steps then all we will
464 // do is log it to the BES log file and not handle the exception with
465 // the exception manager.
466 try {
467 log_status();
468 end_request();
469 }
470 catch (BESError &ex) {
471 ERROR_LOG("Problem logging status or running end of request cleanup: " + ex.get_message());
472 }
473 catch (...) {
474 ERROR_LOG("Unknown problem logging status or running end of request cleanup");
475 }
476
477 return status;
478}
479
486{
487 // now clean up any containers that were used in the request, release
488 // the resource
489 d_dhi_ptr->first_container();
490 while (d_dhi_ptr->container) {
491 d_dhi_ptr->container->release();
492 d_dhi_ptr->next_container();
493 }
494}
495
504void BESInterface::dump(ostream & strm) const
505{
506 strm << BESIndent::LMarg << "BESInterface::dump - (" << (void *) this << ")" << endl;
507 BESIndent::Indent();
508
509 strm << BESIndent::LMarg << "data handler interface:" << endl;
510 BESIndent::Indent();
511 d_dhi_ptr->dump(strm);
512 BESIndent::UnIndent();
513
514 if (d_transmitter) {
515 strm << BESIndent::LMarg << "transmitter:" << endl;
516 BESIndent::Indent();
517 d_transmitter->dump(strm);
518 BESIndent::UnIndent();
519 }
520 else {
521 strm << BESIndent::LMarg << "transmitter: not set" << endl;
522 }
523
524 BESIndent::UnIndent();
525}
526
Structure storing information used by the BES to handle the request.
BESInfo * error_info
error information object
Base exception class for the BES with basic string message.
Definition BESError.h:66
unsigned int get_line() const
get the line number where the exception was thrown
Definition BESError.h:148
unsigned int get_bes_error_type() const
Return the return code for this error class.
Definition BESError.h:174
const char * what() const noexcept override
Return a brief message about the exception.
Definition BESError.h:184
std::string get_file() const
get the file name where the exception was thrown
Definition BESError.h:140
std::string get_message() const
get the error message for this exception
Definition BESError.h:132
virtual void begin_response(const std::string &response_name, BESDataHandlerInterface &dhi)
begin the informational response
Definition BESInfo.cc:120
virtual void add_exception(const BESError &e, const std::string &admin)
add exception information to this informational object
Definition BESInfo.cc:221
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.
BESTransmitter * d_transmitter
The Transmitter to use for the result.
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
error thrown if there is a user syntax error in the request or any other user error
static long get_current_memory_usage() noexcept
Get the Resident Set Size in KB.
Definition BESUtil.cc:89
static std::string & remove_crlf(std::string &str)
"Sanitizes" the string by replacing any 0x0A (new line) or 0x0D (carriage return) characters with 0x2...
Definition BESUtil.cc:1334
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.
static TheBESKeys * TheKeys()
Access to the singleton.
Definition TheBESKeys.cc:85
static int read_int_key(const std::string &key, int default_value)
Read an integer-valued key from the bes.conf file.
static bool read_bool_key(const std::string &key, bool default_value)
Read a boolean-valued key from the bes.conf file.
A ServerAdministrator object from the TheBESKeys associated with the string SERVER_ADMIN_KEY.