Dripline-Cpp  v2.4.2
Dripline Implementation in C++
core.cc
Go to the documentation of this file.
1 /*
2  * core.cc
3  *
4  * Created on: Jun 27, 2017
5  * Author: N.S. Oblath
6  */
7 
8 #define DRIPLINE_API_EXPORTS
9 
10 #include "core.hh"
11 
12 #include "dripline_exceptions.hh"
13 #include "message.hh"
14 
15 #include "authentication.hh"
16 #include "logger.hh"
17 
18 
19 namespace dripline
20 {
21 
22  LOGGER( dlog, "amqp" );
23 
25  {
26  if( f_channel )
27  {
28  try
29  {
30  LDEBUG( dlog, "Stopping consuming messages" );
31  f_channel->BasicCancel( f_consumer_tag );
32  }
33  catch( amqp_exception& e )
34  {
35  LERROR( dlog, "AMQP exception caught while canceling the channel: (" << e.reply_code() << ") " << e.reply_text() );
36  }
37  catch( amqp_lib_exception& e )
38  {
39  LERROR( dlog, "AMQP library exception caught while canceling the channel: (" << e.ErrorCode() << ") " << e.what() );
40  }
41  }
42  }
43 
44  bool core::s_offline = false;
45 
46  core::core( const scarab::param_node& a_config, const std::string& a_broker_address, unsigned a_port, const std::string& a_auth_file, const bool a_make_connection ) :
47  f_address( "localhost" ),
48  f_port( 5672 ),
49  f_username( "guest" ),
50  f_password( "guest" ),
51  f_requests_exchange( "requests" ),
52  f_alerts_exchange( "alerts" ),
53  f_heartbeat_routing_key( "heartbeat" ),
54  f_max_payload_size( DL_MAX_PAYLOAD_SIZE ),
55  f_make_connection( a_make_connection )
56  {
57  // auth file passed as a parameter overrides a file passed in the config
58  std::string t_auth_file( a_auth_file );
59  if( t_auth_file.empty() ) t_auth_file = a_config.get_value( "auth-file", "" );
60 
61  // get auth file contents and override defaults
62  if( ! t_auth_file.empty() )
63  {
64  LDEBUG( dlog, "Using authentication file <" << t_auth_file << ">" );
65 
66  scarab::authentication t_auth( t_auth_file );
67  if( ! t_auth.get_is_loaded() )
68  {
69  throw dripline_error() << "Authentication file <" << a_auth_file << "> could not be loaded";
70  }
71 
72  if( ! t_auth.has( "amqp" ) )
73  {
74  throw dripline_error() << "No \"amqp\" authentication information present in <" << a_auth_file << ">";
75  }
76 
77  const scarab::param_node& t_amqp_auth = t_auth["amqp"].as_node();
78  if( ! t_amqp_auth.has( "username" ) || ! t_amqp_auth.has( "password" ) )
79  {
80  throw dripline_error() << "AMQP authentication is not available or is not complete";
81  }
82  f_username = t_amqp_auth["username"]().as_string();
83  f_password = t_amqp_auth["password"]().as_string();
84 
85  // Override the default values for broker and broker port with those specified in the auth file, if they're there
86  if( t_amqp_auth.has( "broker" ) )
87  {
88  f_address = t_amqp_auth["broker"]().as_string();
89  }
90  if( t_amqp_auth.has( "broker-port" ) )
91  {
92  f_port = t_amqp_auth["broker-port"]().as_uint();
93  }
94  }
95 
96  // config file overrides defaults (and auth file, for broker and broker-port)
97  if( ! a_config.empty() )
98  {
99  f_address = a_config.get_value( "broker", f_address );
100  f_port = a_config.get_value( "broker-port", f_port );
101  f_requests_exchange = a_config.get_value( "requests-exchange", f_requests_exchange );
102  f_alerts_exchange = a_config.get_value( "alerts-exchange", f_alerts_exchange );
103  f_heartbeat_routing_key = a_config.get_value( "heartbeat-routing-key", f_heartbeat_routing_key );
104  f_max_payload_size = a_config.get_value( "max-payload-size", f_max_payload_size );
105  f_make_connection = a_config.get_value( "make-connection", f_make_connection );
106  }
107 
108  // parameters override config file, auth file, and defaults
109  if( ! a_broker_address.empty() ) f_address = a_broker_address;
110  if( a_port != 0 ) f_port = a_port;
111  }
112 
113  core::core( const bool a_make_connection, const scarab::param_node& a_config ) :
114  core::core( a_config )
115  {
116  // this constructor overrides the default value of make_connection
117  f_make_connection = a_make_connection;
118  }
119 
120  core::core( const core& a_orig ) :
121  f_address( a_orig.f_address ),
122  f_port( a_orig.f_port ),
123  f_username( a_orig.f_username ),
124  f_password( a_orig.f_password ),
125  f_requests_exchange( a_orig.f_requests_exchange ),
126  f_alerts_exchange( a_orig.f_alerts_exchange ),
127  f_heartbeat_routing_key( a_orig.f_heartbeat_routing_key ),
128  f_max_payload_size( a_orig.f_max_payload_size ),
129  f_make_connection( a_orig.f_make_connection )
130  {}
131 
132  core::core( core&& a_orig ) :
133  f_address( std::move(a_orig.f_address) ),
134  f_port( a_orig.f_port ),
135  f_username( std::move(a_orig.f_username) ),
136  f_password( std::move(a_orig.f_password) ),
137  f_requests_exchange( std::move(a_orig.f_requests_exchange) ),
138  f_alerts_exchange( std::move(a_orig.f_alerts_exchange) ),
139  f_heartbeat_routing_key( std::move(a_orig.f_heartbeat_routing_key) ),
140  f_max_payload_size( a_orig.f_max_payload_size ),
141  f_make_connection( std::move(a_orig.f_make_connection) )
142  {
143  a_orig.f_port = 0;
144  a_orig.f_max_payload_size = DL_MAX_PAYLOAD_SIZE;
145  }
146 
148  {}
149 
150  core& core::operator=( const core& a_orig )
151  {
152  f_address = a_orig.f_address;
153  f_port = a_orig.f_port;
154  f_username = a_orig.f_username;
155  f_password = a_orig.f_password;
156  f_requests_exchange = a_orig.f_requests_exchange;
157  f_alerts_exchange = a_orig.f_alerts_exchange;
158  f_heartbeat_routing_key = a_orig.f_heartbeat_routing_key;
159  f_max_payload_size = a_orig.f_max_payload_size;
160  f_make_connection = a_orig.f_make_connection;
161  return *this;
162  }
163 
164  core& core::operator=( core&& a_orig )
165  {
166  f_address = std::move( a_orig.f_address );
167  f_port = a_orig.f_port;
168  a_orig.f_port = 0;
169  f_username = std::move( a_orig.f_username );
170  f_password = std::move( a_orig.f_password );
171  f_requests_exchange = std::move( a_orig.f_requests_exchange );
172  f_alerts_exchange = std::move( a_orig.f_alerts_exchange );
173  f_heartbeat_routing_key = std::move( a_orig.f_heartbeat_routing_key );
174  f_max_payload_size = a_orig.f_max_payload_size;
175  a_orig.f_max_payload_size = DL_MAX_PAYLOAD_SIZE;
176  f_make_connection = std::move( a_orig.f_make_connection );
177  return *this;
178  }
179 
181  {
182  LDEBUG( dlog, "Sending request with routing key <" << a_request->routing_key() << ">" );
183  return do_send( std::static_pointer_cast< message >( a_request ), f_requests_exchange, true );
184  }
185 
187  {
188  LDEBUG( dlog, "Sending reply with routing key <" << a_reply->routing_key() << ">" );
189  return do_send( std::static_pointer_cast< message >( a_reply ), f_requests_exchange, false );
190  }
191 
193  {
194  LDEBUG( dlog, "Sending alert with routing key <" << a_alert->routing_key() << ">" );
195  return do_send( std::static_pointer_cast< message >( a_alert ), f_alerts_exchange, false );
196  }
197 
198  sent_msg_pkg_ptr core::do_send( message_ptr_t a_message, const std::string& a_exchange, bool a_expect_reply ) const
199  {
200  // throws connection_error if it could not connect with the broker
201  // throws dripline_error if there's a problem with the exchange or creating the AMQP message object(s)
202  // returns the receive_reply package if the message was completely or partially sent
203  // the f_successful_send flag will be set accordingly: true if completely sent; false if partially sent
204  // if there was an error sending the message, that will be returned in f_send_error_message, which will be empty otherwise
205 
206  // lambda to create a string with the basic information about the send attempt
207  auto t_diagnostic_string_maker = [a_message, this]() -> std::string {
208  return std::string("Broker: ") + f_address +"\nPort: " + std::to_string(f_port) + "\nRouting Key: " + a_message->routing_key();
209  };
210 
211  if ( ! f_make_connection || core::s_offline )
212  {
213  throw a_message;
214  //throw dripline_error() << "cannot send reply when make_connection is false";
215  }
216 
217  amqp_channel_ptr t_channel = open_channel();
218  if( ! t_channel )
219  {
220  throw connection_error() << "Unable to open channel to send message\n" << t_diagnostic_string_maker();
221  }
222 
223  if( ! setup_exchange( t_channel, a_exchange ) )
224  {
225  throw dripline_error() << "Unable to setup the exchange <" << a_exchange << "> to send message\n" << t_diagnostic_string_maker();
226  }
227 
228  // create empty receive-reply object
229  sent_msg_pkg_ptr t_receive_reply = std::make_shared< sent_msg_pkg >();
230  std::unique_lock< std::mutex > t_rr_lock( t_receive_reply->f_mutex );
231 
232  if( a_expect_reply )
233  {
234  t_receive_reply->f_channel = t_channel;
235 
236  // create the reply-to queue, and bind the queue to the routing key over the given exchange
237  std::string t_reply_to = t_channel->DeclareQueue( "" );
238  t_channel->BindQueue( t_reply_to, a_exchange, t_reply_to );
239  // set the reply-to in the message because now we have the queue to which to reply
240  a_message->reply_to() = t_reply_to;
241 
242  // begin consuming on the reply-to queue
243  t_receive_reply->f_consumer_tag = t_channel->BasicConsume( t_reply_to );
244  LDEBUG( dlog, "Reply-to for request: " << t_reply_to );
245  LDEBUG( dlog, "Consumer tag for reply: " << t_receive_reply->f_consumer_tag );
246  }
247 
248  // convert the dripline::message object to an AMQP message
249  amqp_split_message_ptrs t_amqp_messages = a_message->create_amqp_messages( f_max_payload_size );
250  if( t_amqp_messages.empty() )
251  {
252  throw dripline_error() << "Unable to convert the dripline::message object to AMQP message(s) to be sent\n" << t_diagnostic_string_maker();
253  }
254 
255  try
256  {
257  LDEBUG( dlog, "Sending message to <" << a_message->routing_key() << ">" );
258  for( amqp_message_ptr& t_amqp_message : t_amqp_messages )
259  {
260  // send the message
261  // the first boolean argument is whether it's mandatory that the message be delivered to a queue.
262  // this is only the case for requests, where we expect something to be listening.
263  t_channel->BasicPublish( a_exchange, a_message->routing_key(), t_amqp_message, a_message->is_request(), false );
264  }
265  LDEBUG( dlog, "Message sent in " << t_amqp_messages.size() << " chunks" );
266  t_receive_reply->f_successful_send = true;
267  t_receive_reply->f_send_error_message.clear();
268  }
269  catch( AmqpClient::ConnectionClosedException& e )
270  {
271  LERROR( dlog, "Unable to send message because the connection is closed: " << e.what() );
272  throw connection_error() << "Unable to send message because the connection is closed: " << e.what() << '\n' << t_diagnostic_string_maker();
273  }
274  catch( AmqpClient::AmqpLibraryException& e )
275  {
276  LERROR( dlog, "AMQP error while sending message: " << e.what() );
277  t_receive_reply->f_successful_send = false;
278  t_receive_reply->f_send_error_message = std::string("AMQP error while sending message: ") + std::string(e.what()) + '\n' + t_diagnostic_string_maker();
279  }
280  catch( AmqpClient::MessageReturnedException& e )
281  {
282  LERROR( dlog, "Message was returned: " << e.what() );
283  t_receive_reply->f_successful_send = false;
284  t_receive_reply->f_send_error_message = std::string("Message was returned: ") + std::string(e.what()) + '\n' + t_diagnostic_string_maker();
285  }
286  catch( std::exception& e )
287  {
288  LERROR( dlog, "Error while sending message: " << e.what() );
289  t_receive_reply->f_successful_send = false;
290  t_receive_reply->f_send_error_message = std::string("Error while sending message: ") + std::string(e.what()) + '\n' + t_diagnostic_string_maker();
291  }
292 
293  return t_receive_reply;
294  }
295 
297  {
298  if ( ! f_make_connection || core::s_offline )
299  {
300  return amqp_channel_ptr();
301  //throw dripline_error() << "Should not call open_channel when offline";
302  }
303  try
304  {
305  LDEBUG( dlog, "Opening AMQP connection and creating channel to " << f_address << ":" << f_port );
306  LDEBUG( dlog, "Using broker authentication: " << f_username << ":" << f_password );
307  return AmqpClient::Channel::Create( f_address, f_port, f_username, f_password );
308  }
309  catch( amqp_exception& e )
310  {
311  LERROR( dlog, "AMQP exception caught while opening channel: (" << e.reply_code() << ") " << e.reply_text() );
312  return amqp_channel_ptr();
313  }
314  catch( amqp_lib_exception& e )
315  {
316  LERROR( dlog, "AMQP Library Exception caught while creating channel: (" << e.ErrorCode() << ") " << e.what() );
317  if( e.ErrorCode() == -9 )
318  {
319  LERROR( dlog, "This error means the client could not connect to the broker.\n\t" <<
320  "Check that you have the address and port correct, and that the broker is running.")
321  }
322  return amqp_channel_ptr();
323  }
324  catch( std::exception& e )
325  {
326  LERROR( dlog, "Standard exception caught while creating channel: " << e.what() );
327  return amqp_channel_ptr();
328  }
329  }
331  bool core::setup_exchange( amqp_channel_ptr a_channel, const std::string& a_exchange )
332  {
333  if( s_offline || ! a_channel )
334  {
335  return false;
336  }
337 
338  try
339  {
340  LDEBUG( dlog, "Declaring exchange <" << a_exchange << ">" );
341  a_channel->DeclareExchange( a_exchange, AmqpClient::Channel::EXCHANGE_TYPE_TOPIC, false, false, false );
342  return true;
343  }
344  catch( amqp_exception& e )
345  {
346  LERROR( dlog, "AMQP exception caught while declaring exchange: (" << e.reply_code() << ") " << e.reply_text() );
347  return false;
348  }
349  catch( amqp_lib_exception& e )
350  {
351  LERROR( dlog, "AMQP library exception caught while declaring exchange: (" << e.ErrorCode() << ") " << e.what() );
352  return false;
353  }
354  }
356  bool core::setup_queue( amqp_channel_ptr a_channel, const std::string& a_queue_name )
357  {
358  if( s_offline || ! a_channel )
359  {
360  return false;
361  }
362 
363  try
364  {
365  LDEBUG( dlog, "Declaring queue <" << a_queue_name << ">" );
366  a_channel->DeclareQueue( a_queue_name, false, false, true, true );
367  return true;
368  }
369  catch( amqp_exception& e )
370  {
371  LERROR( dlog, "AMQP exception caught while declaring queue: (" << e.reply_code() << ") " << e.reply_text() );
372  return false;
373  }
374  catch( amqp_lib_exception& e )
375  {
376  LERROR( dlog, "AMQP library exception caught while declaring queue: (" << e.ErrorCode() << ") " << e.what() );
377  return false;
378  }
379 
380  }
382  bool core::bind_key( amqp_channel_ptr a_channel, const std::string& a_exchange, const std::string& a_queue_name, const std::string& a_routing_key )
383  {
384  if( s_offline || ! a_channel )
385  {
386  return false;
387  }
388 
389  try
390  {
391  LDEBUG( dlog, "Binding key <" << a_routing_key << "> to queue <" << a_queue_name << "> over exchange <" << a_exchange << ">" );
392  a_channel->BindQueue( a_queue_name, a_exchange, a_routing_key );
393 
394  return true;
395  }
396  catch( amqp_exception& e )
397  {
398  LERROR( dlog, "AMQP exception caught while declaring binding key <" << a_routing_key << ">: (" << e.reply_code() << ") " << e.reply_text() );
399  return false;
400  }
401  catch( amqp_lib_exception& e )
402  {
403  LERROR( dlog, "AMQP library exception caught while binding key <" << a_routing_key << ">: (" << e.ErrorCode() << ") " << e.what() );
404  return false;
405  }
406  }
408  std::string core::start_consuming( amqp_channel_ptr a_channel, const std::string& a_queue_name )
409  {
410  if( s_offline || ! a_channel )
411  {
412  return std::string();
413  }
414 
415  try
416  {
417  LDEBUG( dlog, "Starting to consume messages on queue <" << a_queue_name << ">" );
418  // second bool is setting no_ack to false
419  return a_channel->BasicConsume( a_queue_name, "", true, false );
420  }
421  catch( amqp_exception& e )
422  {
423  LERROR( dlog, "AMQP exception caught while starting consuming messages on <" << a_queue_name << ">: (" << e.reply_code() << ") " << e.reply_text() );
424  return std::string();
425  }
426  catch( amqp_lib_exception& e )
427  {
428  LERROR( dlog, "AMQP library exception caught while starting consuming messages on <" << a_queue_name << ">: (" << e.ErrorCode() << ") " << e.what() );
429  return std::string();
430  }
431  }
433  bool core::stop_consuming( amqp_channel_ptr a_channel, std::string& a_consumer_tag )
434  {
435  if( s_offline || ! a_channel )
436  {
437  return false;
438  }
439 
440  try
441  {
442  LDEBUG( dlog, "Stopping consuming messages for consumer <" << a_consumer_tag << ">" );
443  a_channel->BasicCancel( a_consumer_tag );
444  a_consumer_tag.clear();
445  return true;
446  }
447  catch( amqp_exception& e )
448  {
449  LERROR( dlog, "AMQP exception caught while stopping consuming messages on <" << a_consumer_tag << ">: (" << e.reply_code() << ") " << e.reply_text() );
450  return false;
451  }
452  catch( amqp_lib_exception& e )
453  {
454  LERROR( dlog, "AMQP library exception caught while stopping consuming messages on <" << a_consumer_tag << ">: (" << e.ErrorCode() << ") " << e.what() );
455  return false;
456  }
457  catch( AmqpClient::ConsumerTagNotFoundException& e )
458  {
459  LERROR( dlog, "Fatal AMQP exception encountered while stopping consuming messages on <" << a_consumer_tag << ">: " << e.what() );
460  return false;
461  }
462  catch( std::exception& e )
463  {
464  LERROR( dlog, "Standard exception caught while stopping consuming messages on <" << a_consumer_tag << ">: " << e.what() );
465  return false;
466  }
467  }
469  bool core::remove_queue( amqp_channel_ptr a_channel, const std::string& a_queue_name )
470  {
471  if( s_offline || ! a_channel )
472  {
473  return false;
474  }
475 
476  try
477  {
478  LDEBUG( dlog, "Deleting queue <" << a_queue_name << ">" );
479  a_channel->DeleteQueue( a_queue_name, false );
480  return true;
481  }
482  catch( AmqpClient::ConnectionClosedException& e )
483  {
484  LERROR( dlog, "Fatal AMQP exception encountered removing queue <" << a_queue_name << ">: " << e.what() );
485  return false;
486  }
487  catch( amqp_lib_exception& e )
488  {
489  LERROR( dlog, "AMQP library exception caught while removing queue <" << a_queue_name << ">: (" << e.ErrorCode() << ") " << e.what() );
490  return false;
491  }
492  catch( std::exception& e )
493  {
494  LERROR( dlog, "Standard exception caught while removing queue <" << a_queue_name << ">: " << e.what() );
495  return false;
496  }
497  }
499  bool core::listen_for_message( amqp_envelope_ptr& a_envelope, amqp_channel_ptr a_channel, const std::string& a_consumer_tag, int a_timeout_ms, bool a_do_ack )
500  {
501  if( s_offline || ! a_channel )
502  {
503  return false;
504  }
505 
506  while( true )
507  {
508  try
509  {
510  if( a_timeout_ms > 0 )
511  {
512  a_channel->BasicConsumeMessage( a_consumer_tag, a_envelope, a_timeout_ms );
513  }
514  else
515  {
516  a_envelope = a_channel->BasicConsumeMessage( a_consumer_tag );
517  }
518  if( a_envelope && a_do_ack ) a_channel->BasicAck( a_envelope );
519  return true;
520  }
521  catch( AmqpClient::ConnectionClosedException& e )
522  {
523  LERROR( dlog, "Fatal AMQP exception encountered: " << e.what() );
524  return false;
525  }
526  catch( AmqpClient::ConsumerCancelledException& e )
527  {
528  LERROR( dlog, "Fatal AMQP exception encountered: " << e.what() );
529  return false;
530  }
531  catch( AmqpClient::AmqpException& e )
532  {
533  if( e.is_soft_error() )
534  {
535  LWARN( dlog, "Non-fatal AMQP exception encountered: " << e.reply_text() );
536  return true;
537  }
538  LERROR( dlog, "Fatal AMQP exception encountered: " << e.reply_text() );
539  return false;
540  }
541  catch( std::exception& e )
542  {
543  LERROR( dlog, "Standard exception caught: " << e.what() );
544  return false;
545  }
546  catch(...)
547  {
548  LERROR( dlog, "Unknown exception caught" );
549  return false;
550  }
551  }
552  }
553 
554 } /* namespace dripline */
555 
virtual sent_msg_pkg_ptr send(request_ptr_t a_request) const
Definition: core.cc:180
static bool stop_consuming(amqp_channel_ptr a_channel, std::string &a_consumer_tag)
Definition: core.cc:432
AmqpClient::BasicMessage::ptr_t amqp_message_ptr
Definition: amqp.hh:26
std::shared_ptr< sent_msg_pkg > sent_msg_pkg_ptr
Definition: dripline_fwd.hh:27
static bool setup_exchange(amqp_channel_ptr a_channel, const std::string &a_exchange)
Definition: core.cc:330
STL namespace.
static bool listen_for_message(amqp_envelope_ptr &a_envelope, amqp_channel_ptr a_channel, const std::string &a_consumer_tag, int a_timeout_ms=0, bool a_do_ack=true)
return: if false, channel is no longer useable; if true, may be reused
Definition: core.cc:498
std::shared_ptr< msg_request > request_ptr_t
Definition: dripline_fwd.hh:23
Dripline-specific errors.
std::shared_ptr< msg_alert > alert_ptr_t
Definition: dripline_fwd.hh:25
static bool bind_key(amqp_channel_ptr a_channel, const std::string &a_exchange, const std::string &a_queue_name, const std::string &a_routing_key)
Definition: core.cc:381
sent_msg_pkg_ptr do_send(message_ptr_t a_message, const std::string &a_exchange, bool a_expect_reply) const
Definition: core.cc:198
static std::string start_consuming(amqp_channel_ptr a_channel, const std::string &a_queue_name)
Definition: core.cc:407
static bool remove_queue(amqp_channel_ptr a_channel, const std::string &a_queue_name)
Definition: core.cc:468
amqp_channel_ptr f_channel
Definition: core.hh:40
std::vector< amqp_message_ptr > amqp_split_message_ptrs
Definition: amqp.hh:31
amqp_channel_ptr open_channel() const
Definition: core.cc:296
std::string f_consumer_tag
Definition: core.hh:41
static scarab::logger dlog("agent")
Error indicating a problem with the connection to the broker.
AmqpClient::Channel::ptr_t amqp_channel_ptr
Definition: amqp.hh:24
std::string to_string(op_t an_op)
Gives the human-readable version of a message operation.
AmqpClient::AmqpException amqp_exception
Definition: amqp.hh:28
std::shared_ptr< msg_reply > reply_ptr_t
Definition: dripline_fwd.hh:24
static bool setup_queue(amqp_channel_ptr a_channel, const std::string &a_queue_name)
Definition: core.cc:355
AmqpClient::Envelope::ptr_t amqp_envelope_ptr
Definition: amqp.hh:25
Basic AMQP interactions, including sending messages and interacting with AMQP channels.
Definition: core.hh:72
static bool s_offline
Definition: core.hh:75
AmqpClient::AmqpLibraryException amqp_lib_exception
Definition: amqp.hh:29
core(const scarab::param_node &a_config=scarab::param_node(), const std::string &a_broker_address="", unsigned a_port=0, const std::string &a_auth_file="", const bool a_make_connection=true)
Definition: core.cc:46
std::shared_ptr< message > message_ptr_t
Definition: dripline_fwd.hh:20
#define DL_MAX_PAYLOAD_SIZE
core & operator=(const core &a_orig)
Definition: core.cc:150
virtual ~core()
Definition: core.cc:147