1 // Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") 2 // 3 // This Source Code Form is subject to the terms of the Mozilla Public 4 // License, v. 2.0. If a copy of the MPL was not distributed with this 5 // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 7 #ifndef NCR_IO_H 8 #define NCR_IO_H 9 10 /// @file ncr_io.h 11 /// @brief This file defines abstract classes for exchanging NameChangeRequests. 12 /// 13 /// These classes are used for sending and receiving requests to update DNS 14 /// information for FQDNs embodied as NameChangeRequests (aka NCRs). Ultimately, 15 /// NCRs must move through the following three layers in order to implement 16 /// DHCP-DDNS: 17 /// 18 /// * Application layer - the business layer which needs to 19 /// transport NameChangeRequests, and is unaware of the means by which 20 /// they are transported. 21 /// 22 /// * NameChangeRequest layer - This is the layer which acts as the 23 /// intermediary between the Application layer and the IO layer. It must 24 /// be able to move NameChangeRequests to the IO layer as raw data and move 25 /// raw data from the IO layer in the Application layer as 26 /// NameChangeRequests. 27 /// 28 /// * IO layer - the low-level layer that is directly responsible for 29 /// sending and receiving data asynchronously and is supplied through 30 /// other libraries. This layer is largely unaware of the nature of the 31 /// data being transmitted. In other words, it doesn't know beans about 32 /// NCRs. 33 /// 34 /// The abstract classes defined here implement the latter, middle layer, 35 /// the NameChangeRequest layer. There are two types of participants in this 36 /// middle ground: 37 /// 38 /// * listeners - Receive NCRs from one or more sources. The DHCP-DDNS 39 /// application, (aka D2), is a listener. Listeners are embodied by the 40 /// class, NameChangeListener. 41 /// 42 /// * senders - sends NCRs to a given target. DHCP servers are senders. 43 /// Senders are embodied by the class, NameChangeSender. 44 /// 45 /// These two classes present a public interface for asynchronous 46 /// communications that is independent of the IO layer mechanisms. While the 47 /// type and details of the IO mechanism are not relevant to either class, it 48 /// is presumed to use isc::asiolink library for asynchronous event processing. 49 50 #include <asiolink/io_address.h> 51 #include <asiolink/io_service.h> 52 #include <dhcp_ddns/ncr_msg.h> 53 #include <exceptions/exceptions.h> 54 55 #include <boost/scoped_ptr.hpp> 56 57 #include <deque> 58 #include <mutex> 59 60 namespace isc { 61 namespace dhcp_ddns { 62 63 /// @brief Defines the list of socket protocols supported. 64 /// Currently only UDP is implemented. 65 /// @todo TCP is intended to be implemented prior 1.0 release. 66 /// @todo Give some thought to an ANY protocol which might try 67 /// first as UDP then as TCP, etc. 68 enum NameChangeProtocol { 69 NCR_UDP, 70 NCR_TCP 71 }; 72 73 /// @brief Function which converts text labels to @ref NameChangeProtocol enums. 74 /// 75 /// @param protocol_str text to convert to an enum. 76 /// Valid string values: "UDP", "TCP" 77 /// 78 /// @return NameChangeProtocol value which maps to the given string. 79 /// 80 /// @throw isc::BadValue if given a string value which does not map to an 81 /// enum value. 82 extern NameChangeProtocol stringToNcrProtocol(const std::string& protocol_str); 83 84 /// @brief Function which converts @ref NameChangeProtocol enums to text labels. 85 /// 86 /// @param protocol enum value to convert to label 87 /// 88 /// @return std:string containing the text label if the value is valid, or 89 /// "UNKNOWN" if not. 90 extern std::string ncrProtocolToString(NameChangeProtocol protocol); 91 92 /// @brief Exception thrown if an NcrListenerError encounters a general error. 93 class NcrListenerError : public isc::Exception { 94 public: NcrListenerError(const char * file,size_t line,const char * what)95 NcrListenerError(const char* file, size_t line, const char* what) : 96 isc::Exception(file, line, what) { }; 97 }; 98 99 /// @brief Exception thrown if an error occurs during IO source open. 100 class NcrListenerOpenError : public isc::Exception { 101 public: NcrListenerOpenError(const char * file,size_t line,const char * what)102 NcrListenerOpenError(const char* file, size_t line, const char* what) : 103 isc::Exception(file, line, what) { }; 104 }; 105 106 /// @brief Exception thrown if an error occurs initiating an IO receive. 107 class NcrListenerReceiveError : public isc::Exception { 108 public: NcrListenerReceiveError(const char * file,size_t line,const char * what)109 NcrListenerReceiveError(const char* file, size_t line, const char* what) : 110 isc::Exception(file, line, what) { }; 111 }; 112 113 114 /// @brief Abstract interface for receiving NameChangeRequests. 115 /// 116 /// NameChangeListener provides the means to: 117 /// - Supply a callback to invoke upon receipt of an NCR or a listening 118 /// error 119 /// - Start listening using a given IOService instance to process events 120 /// - Stop listening 121 /// 122 /// It implements the high level logic flow to listen until a request arrives, 123 /// invoke the implementation's handler and return to listening for the next 124 /// request. 125 /// 126 /// It provides virtual methods that allow derivations supply implementations 127 /// to open the appropriate IO source, perform a listen, and close the IO 128 /// source. 129 /// 130 /// The overall design is based on a callback chain. The listener's caller (the 131 /// application) supplies an "application" layer callback through which it will 132 /// receive inbound NameChangeRequests. The listener derivation will supply 133 /// its own callback to the IO layer to process receive events from its IO 134 /// source. This is referred to as the NameChangeRequest completion handler. 135 /// It is through this handler that the NameChangeRequest layer gains access 136 /// to the low level IO read service results. It is expected to assemble 137 /// NameChangeRequests from the inbound data and forward them to the 138 /// application layer by invoking the application layer callback registered 139 /// with the listener. 140 /// 141 /// The application layer callback is structured around a nested class, 142 /// RequestReceiveHandler. It consists of single, abstract operator() which 143 /// accepts a result code and a pointer to a NameChangeRequest as parameters. 144 /// In order to receive inbound NCRs, a caller implements a derivation of the 145 /// RequestReceiveHandler and supplies an instance of this derivation to the 146 /// NameChangeListener constructor. This "registers" the handler with the 147 /// listener. 148 /// 149 /// To begin listening, the caller invokes the listener's startListener() 150 /// method, passing in an IOService instance. This in turn will pass the 151 /// IOService into the virtual method, open(). The open method is where the 152 /// listener derivation performs the steps necessary to prepare its IO source 153 /// for reception (e.g. opening a socket, connecting to a database). 154 /// 155 /// Assuming the open is successful, startListener will call receiveNext, to 156 /// initiate an asynchronous receive. This method calls the virtual method, 157 /// doReceive(). The listener derivation uses doReceive to instigate an IO 158 /// layer asynchronous receive passing in its IO layer callback to 159 /// handle receive events from the IO source. 160 /// 161 /// As stated earlier, the derivation's NameChangeRequest completion handler 162 /// MUST invoke the application layer handler registered with the listener. 163 /// This is done by passing in either a success status and a populated 164 /// NameChangeRequest or an error status and an empty request into the 165 /// listener's invokeRecvHandler method. This is the mechanism by which the 166 /// listener's caller is handed inbound NCRs. 167 class NameChangeListener { 168 public: 169 170 /// @brief Defines the outcome of an asynchronous NCR receive 171 enum Result { 172 SUCCESS, 173 TIME_OUT, 174 STOPPED, 175 ERROR 176 }; 177 178 /// @brief Abstract class for defining application layer receive callbacks. 179 /// 180 /// Applications which will receive NameChangeRequests must provide a 181 /// derivation of this class to the listener constructor in order to 182 /// receive NameChangeRequests. 183 class RequestReceiveHandler { 184 public: 185 186 /// @brief Function operator implementing a NCR receive callback. 187 /// 188 /// This method allows the application to receive the inbound 189 /// NameChangeRequests. It is intended to function as a hand off of 190 /// information and should probably not be time-consuming. 191 /// 192 /// @param result contains that receive outcome status. 193 /// @param ncr is a pointer to the newly received NameChangeRequest if 194 /// result is NameChangeListener::SUCCESS. It is indeterminate other 195 /// wise. 196 /// 197 /// @throw This method MUST NOT throw. 198 virtual void operator ()(const Result result, 199 NameChangeRequestPtr& ncr) = 0; 200 ~RequestReceiveHandler()201 virtual ~RequestReceiveHandler() { 202 } 203 }; 204 205 /// @brief Constructor 206 /// 207 /// @param recv_handler is a pointer the application layer handler to be 208 /// invoked each time a NCR is received or a receive error occurs. 209 NameChangeListener(RequestReceiveHandler& recv_handler); 210 211 /// @brief Destructor ~NameChangeListener()212 virtual ~NameChangeListener() { 213 }; 214 215 /// @brief Prepares the IO for reception and initiates the first receive. 216 /// 217 /// Calls the derivation's open implementation to initialize the IO layer 218 /// source for receiving inbound requests. If successful, it starts the 219 /// first asynchronous read by receiveNext. 220 /// 221 /// @param io_service is the IOService that will handle IO event processing. 222 /// 223 /// @throw NcrListenError if the listener is already "listening" or 224 /// in the event the open or doReceive methods fail. 225 void startListening(isc::asiolink::IOService& io_service); 226 227 /// @brief Closes the IO source and stops listen logic. 228 /// 229 /// Calls the derivation's implementation of close and marks the state 230 /// as not listening. 231 void stopListening(); 232 233 protected: 234 /// @brief Initiates an asynchronous receive 235 /// 236 /// Sets context information to indicate that IO is in progress and invokes 237 /// the derivation's asynchronous receive method, doReceive. Note doReceive 238 /// should not be called outside this method to ensure context information 239 /// integrity. 240 /// 241 /// @throw Derivation's doReceive method may throw isc::Exception upon 242 /// error. 243 void receiveNext(); 244 245 /// @brief Calls the NCR receive handler registered with the listener. 246 /// 247 /// This is the hook by which the listener's caller's NCR receive handler 248 /// is called. This method MUST be invoked by the derivation's 249 /// implementation of doReceive. 250 /// 251 /// NOTE: 252 /// The handler invoked by this method MUST NOT THROW. The handler is 253 /// at application level and should trap and handle any errors at 254 /// that level, rather than throw exceptions. If an error has occurred 255 /// prior to invoking the handler, it will be expressed in terms a failed 256 /// result being passed to the handler, not a throw. Therefore any 257 /// exceptions at the handler level are application issues and should be 258 /// dealt with at that level. 259 /// 260 /// This method does wrap the handler invocation within a try-catch 261 /// block as a fail-safe. The exception will be logged but the 262 /// receive logic will continue. What this implies is that continued 263 /// operation may or may not succeed as the application has violated 264 /// the interface contract. 265 /// 266 /// @param result contains that receive outcome status. 267 /// @param ncr is a pointer to the newly received NameChangeRequest if 268 /// result is NameChangeListener::SUCCESS. It is indeterminate other 269 /// wise. 270 void invokeRecvHandler(const Result result, NameChangeRequestPtr& ncr); 271 272 /// @brief Abstract method which opens the IO source for reception. 273 /// 274 /// The derivation uses this method to perform the steps needed to 275 /// prepare the IO source to receive requests. 276 /// 277 /// @param io_service is the IOService that process IO events. 278 /// 279 /// @throw If the implementation encounters an error it MUST 280 /// throw it as an isc::Exception or derivative. 281 virtual void open(isc::asiolink::IOService& io_service) = 0; 282 283 /// @brief Abstract method which closes the IO source. 284 /// 285 /// The derivation uses this method to perform the steps needed to 286 /// "close" the IO source. 287 /// 288 /// @throw If the implementation encounters an error it MUST 289 /// throw it as an isc::Exception or derivative. 290 virtual void close() = 0; 291 292 /// @brief Initiates an IO layer asynchronous read. 293 /// 294 /// The derivation uses this method to perform the steps needed to 295 /// initiate an asynchronous read of the IO source with the 296 /// derivation's IO layer handler as the IO completion callback. 297 /// 298 /// @throw If the implementation encounters an error it MUST 299 /// throw it as an isc::Exception or derivative. 300 virtual void doReceive() = 0; 301 302 public: 303 304 /// @brief Returns true if the listener is listening, false otherwise. 305 /// 306 /// A true value indicates that the IO source has been opened successfully, 307 /// and that receive loop logic is active. This implies that closing the 308 /// IO source will interrupt that operation, resulting in a callback 309 /// invocation. 310 /// 311 /// @return The listening mode. amListening()312 bool amListening() const { 313 return (listening_); 314 } 315 316 /// @brief Returns true if the listener has an IO call in progress. 317 /// 318 /// A true value indicates that the listener has an asynchronous IO in 319 /// progress which will complete at some point in the future. Completion 320 /// of the call will invoke the registered callback. It is important to 321 /// understand that the listener and its related objects should not be 322 /// deleted while there is an IO call pending. This can result in the 323 /// IO service attempting to invoke methods on objects that are no longer 324 /// valid. 325 /// 326 /// @return The IO pending flag. isIoPending()327 bool isIoPending() const { 328 return (io_pending_); 329 } 330 331 private: 332 /// @brief Sets the listening indicator to the given value. 333 /// 334 /// Note, this method is private as it is used the base class is solely 335 /// responsible for managing the state. 336 /// 337 /// @param value is the new value to assign to the indicator. setListening(bool value)338 void setListening(bool value) { 339 listening_ = value; 340 } 341 342 /// @brief Indicates if the listener is in listening mode. 343 bool listening_; 344 345 /// @brief Indicates that listener has an async IO pending completion. 346 bool io_pending_; 347 348 /// @brief Application level NCR receive completion handler. 349 RequestReceiveHandler& recv_handler_; 350 }; 351 352 /// @brief Defines a smart pointer to an instance of a listener. 353 typedef boost::shared_ptr<NameChangeListener> NameChangeListenerPtr; 354 355 356 /// @brief Thrown when a NameChangeSender encounters an error. 357 class NcrSenderError : public isc::Exception { 358 public: NcrSenderError(const char * file,size_t line,const char * what)359 NcrSenderError(const char* file, size_t line, const char* what) : 360 isc::Exception(file, line, what) { }; 361 }; 362 363 /// @brief Exception thrown if an error occurs during IO source open. 364 class NcrSenderOpenError : public isc::Exception { 365 public: NcrSenderOpenError(const char * file,size_t line,const char * what)366 NcrSenderOpenError(const char* file, size_t line, const char* what) : 367 isc::Exception(file, line, what) { }; 368 }; 369 370 /// @brief Exception thrown if an error occurs initiating an IO send. 371 class NcrSenderQueueFull : public isc::Exception { 372 public: NcrSenderQueueFull(const char * file,size_t line,const char * what)373 NcrSenderQueueFull(const char* file, size_t line, const char* what) : 374 isc::Exception(file, line, what) { }; 375 }; 376 377 /// @brief Exception thrown if an error occurs initiating an IO send. 378 class NcrSenderSendError : public isc::Exception { 379 public: NcrSenderSendError(const char * file,size_t line,const char * what)380 NcrSenderSendError(const char* file, size_t line, const char* what) : 381 isc::Exception(file, line, what) { }; 382 }; 383 384 385 /// @brief Abstract interface for sending NameChangeRequests. 386 /// 387 /// NameChangeSender provides the means to: 388 /// - Supply a callback to invoke upon completing the delivery of an NCR or a 389 /// send error 390 /// - Start sending using a given IOService instance to process events 391 /// - Queue NCRs for delivery 392 /// - Stop sending 393 /// 394 /// It implements the high level logic flow to queue requests for delivery, 395 /// and ship them one at a time, waiting for the send to complete prior to 396 /// sending the next request in the queue. If a send fails, the request 397 /// will remain at the front of queue and the send will be retried 398 /// endlessly unless the caller dequeues the request. Note, it is presumed that 399 /// a send failure is some form of IO error such as loss of connectivity and 400 /// not a message content error. It should not be possible to queue an invalid 401 /// message. 402 /// 403 /// It should be noted that once a request is placed onto the send queue it 404 /// will remain there until one of three things occur: 405 /// * It is successfully delivered 406 /// * @c NameChangeSender::skipNext() is called 407 /// * @c NameChangeSender::clearSendQueue() is called 408 /// 409 /// The queue contents are preserved across start and stop listening 410 /// transitions. This is to provide for error recovery without losing 411 /// undelivered requests. 412 413 /// It provides virtual methods so derivations may supply implementations to 414 /// open the appropriate IO sink, perform a send, and close the IO sink. 415 /// 416 /// The overall design is based on a callback chain. The sender's caller (the 417 /// application) supplies an "application" layer callback through which it will 418 /// be given send completion notifications. The sender derivation will employ 419 /// its own callback at the IO layer to process send events from its IO sink. 420 /// This callback is expected to forward the outcome of each asynchronous send 421 /// to the application layer by invoking the application layer callback 422 /// registered with the sender. 423 /// 424 /// The application layer callback is structured around a nested class, 425 /// RequestSendHandler. It consists of single, abstract operator() which 426 /// accepts a result code and a pointer to a NameChangeRequest as parameters. 427 /// In order to receive send completion notifications, a caller implements a 428 /// derivation of the RequestSendHandler and supplies an instance of this 429 /// derivation to the NameChangeSender constructor. This "registers" the 430 /// handler with the sender. 431 /// 432 /// To begin sending, the caller invokes the listener's startSending() 433 /// method, passing in an IOService instance. This in turn will pass the 434 /// IOService into the virtual method, open(). The open method is where the 435 /// sender derivation performs the steps necessary to prepare its IO sink for 436 /// output (e.g. opening a socket, connecting to a database). At this point, 437 /// the sender is ready to send messages. 438 /// 439 /// In order to send a request, the application layer invokes the sender 440 /// method, sendRequest(), passing in the NameChangeRequest to send. This 441 /// method places the request onto the back of the send queue, and then invokes 442 /// the sender method, sendNext(). 443 /// 444 /// If there is already a send in progress when sendNext() is called, the method 445 /// will return immediately rather than initiate the next send. This is to 446 /// ensure that sends are processed sequentially. 447 /// 448 /// If there is not a send in progress and the send queue is not empty, 449 /// the sendNext method will pass the NCR at the front of the send queue into 450 /// the virtual doSend() method. 451 /// 452 /// The sender derivation uses this doSend() method to instigate an IO layer 453 /// asynchronous send with its IO layer callback to handle send events from its 454 /// IO sink. 455 /// 456 /// As stated earlier, the derivation's IO layer callback MUST invoke the 457 /// application layer handler registered with the sender. This is done by 458 /// passing in a status indicating the outcome of the send into the sender's 459 /// invokeSendHandler method. This is the mechanism by which the sender's 460 /// caller is handed outbound notifications. 461 462 /// After invoking the application layer handler, the invokeSendHandler method 463 /// will call the sendNext() method to initiate the next send. This ensures 464 /// that requests continue to dequeue and ship. 465 /// 466 class NameChangeSender { 467 public: 468 469 /// @brief Defines the type used for the request send queue. 470 typedef std::deque<NameChangeRequestPtr> SendQueue; 471 472 /// @brief Defines a default maximum number of entries in the send queue. 473 static const size_t MAX_QUEUE_DEFAULT = 1024; 474 475 /// @brief Defines the outcome of an asynchronous NCR send. 476 enum Result { 477 SUCCESS, 478 TIME_OUT, 479 STOPPED, 480 ERROR 481 }; 482 483 /// @brief Abstract class for defining application layer send callbacks. 484 /// 485 /// Applications which will send NameChangeRequests must provide a 486 /// derivation of this class to the sender constructor in order to 487 /// receive send outcome notifications. 488 class RequestSendHandler { 489 public: 490 491 /// @brief Function operator implementing a NCR send callback. 492 /// 493 /// This method allows the application to receive the outcome of 494 /// each send. It is intended to function as a hand off of information 495 /// and should probably not be time-consuming. 496 /// 497 /// @param result contains that send outcome status. 498 /// @param ncr is a pointer to the NameChangeRequest that was 499 /// delivered (or attempted). 500 /// 501 /// @throw This method MUST NOT throw. 502 virtual void operator ()(const Result result, 503 NameChangeRequestPtr& ncr) = 0; 504 ~RequestSendHandler()505 virtual ~RequestSendHandler() { 506 } 507 }; 508 509 /// @brief Constructor 510 /// 511 /// @param send_handler is a pointer the application layer handler to be 512 /// invoked each time a NCR send attempt completes. 513 /// @param send_queue_max is the maximum number of entries allowed in the 514 /// send queue. Once the maximum number is reached, all calls to 515 /// sendRequest will fail with an exception. 516 NameChangeSender(RequestSendHandler& send_handler, 517 size_t send_queue_max = MAX_QUEUE_DEFAULT); 518 519 /// @brief Destructor ~NameChangeSender()520 virtual ~NameChangeSender() { 521 } 522 523 /// @brief Prepares the IO for transmission. 524 /// 525 /// Calls the derivation's open implementation to initialize the IO layer 526 /// sink for sending outbound requests. 527 /// 528 /// @param io_service is the IOService that will handle IO event processing. 529 /// 530 /// @throw NcrSenderError if the sender is already "sending" or 531 /// NcrSenderOpenError if the open fails. 532 void startSending(isc::asiolink::IOService & io_service); 533 534 /// @brief Closes the IO sink and stops send logic. 535 /// 536 /// Calls the derivation's implementation of close and marks the state 537 /// as not sending. 538 void stopSending(); 539 540 /// @brief Queues the given request to be sent. 541 /// 542 /// The given request is placed at the back of the send queue and then 543 /// sendNext is invoked. 544 /// 545 /// @param ncr is the NameChangeRequest to send. 546 /// 547 /// @throw NcrSenderError if the sender is not in sending state or 548 /// the request is empty; NcrSenderQueueFull if the send queue has reached 549 /// capacity. 550 void sendRequest(NameChangeRequestPtr& ncr); 551 552 /// @brief Move all queued requests from a given sender into the send queue 553 /// 554 /// Moves all of the entries in the given sender's queue and places them 555 /// into send queue. This provides a mechanism of reassigning queued 556 /// messages from one sender to another. This is useful for dealing with 557 /// dynamic configuration changes. 558 /// 559 /// @param source_sender from whom the queued messages will be taken 560 /// 561 /// @throw NcrSenderError if either sender is in send mode, if the number of 562 /// messages in the source sender's queue is larger than this sender's 563 /// maximum queue size, or if this sender's queue is not empty. 564 void assumeQueue(NameChangeSender& source_sender); 565 566 /// @brief Returns a file descriptor suitable for use with select 567 /// 568 /// The value returned is an open file descriptor which can be used with 569 /// select() system call to monitor the sender for IO events. This allows 570 /// NameChangeSenders to be used in applications which use select, rather 571 /// than IOService to wait for IO events to occur. 572 /// 573 /// @warning Attempting other use of this value may lead to unpredictable 574 /// behavior in the sender. 575 /// 576 /// @return Returns an "open" file descriptor 577 /// 578 /// @throw NcrSenderError if the sender is not in send mode, 579 virtual int getSelectFd() = 0; 580 581 /// @brief Returns whether or not the sender has IO ready to process. 582 /// 583 /// @return true if the sender has at IO ready, false otherwise. 584 virtual bool ioReady() = 0; 585 586 private: 587 588 /// @brief Prepares the IO for transmission in a thread safe context. 589 /// 590 /// @param io_service is the IOService that will handle IO event processing. 591 void startSendingInternal(isc::asiolink::IOService & io_service); 592 593 /// @brief Queues the given request to be sent in a thread safe context. 594 /// 595 /// @param ncr is the NameChangeRequest to send. 596 /// 597 /// @throw NcrSenderQueueFull if the send queue has reached capacity. 598 void sendRequestInternal(NameChangeRequestPtr& ncr); 599 600 /// @brief Move all queued requests from a given sender into the send queue 601 /// in a thread safe context. 602 /// 603 /// @param source_sender from whom the queued messages will be taken 604 /// 605 /// @throw NcrSenderError if this sender's queue is not empty. 606 void assumeQueueInternal(NameChangeSender& source_sender); 607 608 /// @brief Calls the NCR send completion handler registered with the 609 /// sender in a thread safe context. 610 /// 611 /// @param result contains that send outcome status. 612 void invokeSendHandlerInternal(const NameChangeSender::Result result); 613 614 /// @brief Removes the request at the front of the send queue in a thread 615 /// safe context. 616 void skipNextInternal(); 617 618 /// @brief Returns the number of entries currently in the send queue in a 619 /// thread safe context. 620 /// 621 /// @return the queue size. 622 size_t getQueueSizeInternal() const; 623 624 /// @brief Returns the entry at a given position in the queue in a thread 625 /// safe context. 626 /// 627 /// @return Pointer reference to the queue entry. 628 /// 629 /// @throw NcrSenderError if the given index is beyond the 630 /// end of the queue. 631 const NameChangeRequestPtr& peekAtInternal(const size_t index) const; 632 633 protected: 634 635 /// @brief Dequeues and sends the next request on the send queue in a thread 636 /// safe context. 637 /// 638 /// If there is already a send in progress just return. If there is not 639 /// a send in progress and the send queue is not empty the grab the next 640 /// message on the front of the queue and call doSend(). 641 void sendNext(); 642 643 /// @brief Calls the NCR send completion handler registered with the 644 /// sender. 645 /// 646 /// This is the hook by which the sender's caller's NCR send completion 647 /// handler is called. This method MUST be invoked by the derivation's 648 /// implementation of doSend. Note that if the send was a success, the 649 /// entry at the front of the queue is removed from the queue. 650 /// If not we leave it there so we can retry it. After we invoke the 651 /// handler we clear the pending ncr value and queue up the next send. 652 /// 653 /// NOTE: 654 /// The handler invoked by this method MUST NOT THROW. The handler is 655 /// application level logic and should trap and handle any errors at 656 /// that level, rather than throw exceptions. If IO errors have occurred 657 /// prior to invoking the handler, they are expressed in terms a failed 658 /// result being passed to the handler. Therefore any exceptions at the 659 /// handler level are application issues and should be dealt with at that 660 /// level. 661 /// 662 /// This method does wrap the handler invocation within a try-catch 663 /// block as a fail-safe. The exception will be logged but the 664 /// send logic will continue. What this implies is that continued 665 /// operation may or may not succeed as the application has violated 666 /// the interface contract. 667 /// 668 /// @param result contains that send outcome status. 669 void invokeSendHandler(const NameChangeSender::Result result); 670 671 /// @brief Abstract method which opens the IO sink for transmission. 672 /// 673 /// The derivation uses this method to perform the steps needed to 674 /// prepare the IO sink to send requests. 675 /// 676 /// @param io_service is the IOService that process IO events. 677 /// 678 /// @throw If the implementation encounters an error it MUST 679 /// throw it as an isc::Exception or derivative. 680 virtual void open(isc::asiolink::IOService& io_service) = 0; 681 682 /// @brief Abstract method which closes the IO sink. 683 /// 684 /// The derivation uses this method to perform the steps needed to 685 /// "close" the IO sink. 686 /// 687 /// @throw If the implementation encounters an error it MUST 688 /// throw it as an isc::Exception or derivative. 689 virtual void close() = 0; 690 691 /// @brief Initiates an IO layer asynchronous send 692 /// 693 /// The derivation uses this method to perform the steps needed to 694 /// initiate an asynchronous send through the IO sink of the given NCR. 695 /// 696 /// @param ncr is a pointer to the NameChangeRequest to send. 697 /// derivation's IO layer handler as the IO completion callback. 698 /// 699 /// @throw If the implementation encounters an error it MUST 700 /// throw it as an isc::Exception or derivative. 701 virtual void doSend(NameChangeRequestPtr& ncr) = 0; 702 703 public: 704 705 /// @brief Removes the request at the front of the send queue 706 /// 707 /// This method can be used to avoid further retries of a failed 708 /// send. It is provided primarily as a just-in-case measure. Since 709 /// a failed send results in the same request being retried continuously 710 /// this method makes it possible to remove that entry, causing the 711 /// subsequent entry in the queue to be attempted on the next send. 712 /// It is presumed that sends will only fail due to some sort of 713 /// communications issue. In the unlikely event that a request is 714 /// somehow tainted and causes an send failure based on its content, 715 /// this method provides a means to remove the message. 716 void skipNext(); 717 718 /// @brief Flushes all entries in the send queue 719 /// 720 /// This method can be used to discard all of the NCRs currently in the 721 /// the send queue. Note it may not be called while the sender is in 722 /// the sending state. 723 /// 724 /// @throw NcrSenderError if called and sender is in sending state. 725 void clearSendQueue(); 726 727 /// @brief Returns true if the sender is in send mode, false otherwise. 728 /// 729 /// A true value indicates that the IO sink has been opened successfully, 730 /// and that send loop logic is active. 731 /// 732 /// @return The send mode. amSending()733 bool amSending() const { 734 return (sending_); 735 } 736 737 /// @brief Returns true when a send is in progress. 738 /// 739 /// A true value indicates that a request is actively in the process of 740 /// being delivered. 741 /// 742 /// @return The send in progress flag. 743 bool isSendInProgress() const; 744 745 /// @brief Returns the maximum number of entries allowed in the send queue. 746 /// 747 /// @return The queue maximum size. getQueueMaxSize()748 size_t getQueueMaxSize() const { 749 return (send_queue_max_); 750 } 751 752 /// @brief Sets the maximum queue size to the given value. 753 /// 754 /// Sets the maximum number of entries allowed in the queue to the 755 /// the given value. 756 /// 757 /// @param new_max the new value to use as the maximum 758 /// 759 /// @throw NcrSenderError if the value is less than one. 760 void setQueueMaxSize(const size_t new_max); 761 762 /// @brief Returns the number of entries currently in the send queue. 763 /// 764 /// @return The queue size. 765 size_t getQueueSize() const; 766 767 /// @brief Returns the entry at a given position in the queue. 768 /// 769 /// Note that the entry is not removed from the queue. 770 /// 771 /// @param index the index of the entry in the queue to fetch. 772 /// Valid values are 0 (front of the queue) to (queue size - 1). 773 /// 774 /// @return Pointer reference to the queue entry. 775 /// 776 /// @throw NcrSenderError if the given index is beyond the 777 /// end of the queue. 778 const NameChangeRequestPtr& peekAt(const size_t index) const; 779 780 /// @brief Processes sender IO events 781 /// 782 /// Executes at most one ready handler on the sender's IO service. If 783 /// no handlers are ready it returns immediately. 784 /// 785 /// @warning - Running all ready handlers, in theory, could process all 786 /// messages currently queued. 787 /// 788 /// NameChangeSender daisy chains requests together in its completion 789 /// by one message completion's handler initiating the next message's send. 790 /// When using UDP, a send immediately marks its event handler as ready 791 /// to run. If this occurs inside a call to ioservice::poll() or run(), 792 /// that event will also be run. If that handler calls UDP send then 793 /// that send's handler will be marked ready and executed and so on. If 794 /// there were 1000 messages in the queue then all them would be sent from 795 /// within the context of one call to runReadyIO(). 796 /// By running only one handler at time, we ensure that NCR IO activity 797 /// doesn't starve other processing. It is unclear how much of a real 798 /// threat this poses but for now it is best to err on the side of caution. 799 virtual void runReadyIO(); 800 801 protected: 802 803 /// @brief Returns a reference to the send queue. 804 /// 805 /// @return The send queue. getSendQueue()806 SendQueue& getSendQueue() { 807 return (send_queue_); 808 } 809 810 private: 811 812 /// @brief Sets the sending indicator to the given value. 813 /// 814 /// Note, this method is private as it is used the base class is solely 815 /// responsible for managing the state. 816 /// 817 /// @param value is the new value to assign to the indicator. setSending(bool value)818 void setSending(bool value) { 819 sending_ = value; 820 } 821 822 /// @brief Boolean indicator which tracks sending status. 823 bool sending_; 824 825 /// @brief A pointer to registered send completion handler. 826 RequestSendHandler& send_handler_; 827 828 /// @brief Maximum number of entries permitted in the send queue. 829 size_t send_queue_max_; 830 831 /// @brief Queue of the requests waiting to be sent. 832 SendQueue send_queue_; 833 834 /// @brief Pointer to the request which is in the process of being sent. 835 NameChangeRequestPtr ncr_to_send_; 836 837 /// @brief Pointer to the IOService currently being used by the sender. 838 /// @note We need to remember the io_service but we receive it by 839 /// reference. Use a raw pointer to store it. This value should never be 840 /// exposed and is only valid while in send mode. 841 asiolink::IOService* io_service_; 842 843 /// @brief The mutex used to protect internal state. 844 const boost::scoped_ptr<std::mutex> mutex_; 845 }; 846 847 /// @brief Defines a smart pointer to an instance of a sender. 848 typedef boost::shared_ptr<NameChangeSender> NameChangeSenderPtr; 849 850 } // namespace dhcp_ddns 851 } // namespace isc 852 853 #endif 854