ncr_io.cc 9.27 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

#include <d2/d2_log.h>
#include <d2/ncr_io.h>

namespace isc {
namespace d2 {

//************************** NameChangeListener ***************************

23
NameChangeListener::NameChangeListener(RequestReceiveHandler&
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
                                       recv_handler)
    : listening_(false), recv_handler_(recv_handler) {
};

void
NameChangeListener::startListening(isc::asiolink::IOService& io_service) {
    if (amListening()) {
        // This amounts to a programmatic error.
        isc_throw(NcrListenerError, "NameChangeListener is already listening");
    }

    // Call implementation dependent open.
    try {
        open(io_service);
    } catch (const isc::Exception& ex) {
        stopListening();
        isc_throw(NcrListenerOpenError, "Open failed:" << ex.what());
    }

    // Set our status to listening.
    setListening(true);

    // Start the first asynchronous receive.
    try {
        doReceive();
    } catch (const isc::Exception& ex) {
        stopListening();
        isc_throw(NcrListenerReceiveError, "doReceive failed:" << ex.what());
    }
}

void
NameChangeListener::stopListening() {
    try {
        // Call implementation dependent close.
        close();
    } catch (const isc::Exception &ex) {
        // Swallow exceptions. If we have some sort of error we'll log
        // it but we won't propagate the throw.
63
        LOG_ERROR(dctl_logger, DHCP_DDNS_NCR_LISTEN_CLOSE_ERROR).arg(ex.what());
64
65
    }

66
67
    // Set it false, no matter what.  This allows us to at least try to
    // re-open via startListening().
68
69
70
71
    setListening(false);
}

void
72
NameChangeListener::invokeRecvHandler(const Result result,
73
                                      NameChangeRequestPtr& ncr) {
74
    // Call the registered application layer handler.
75
76
77
78
79
80
81
82
83
    // Surround the invocation with a try-catch. The invoked handler is
    // not supposed to throw, but in the event it does we will at least
    // report it.
    try {
        recv_handler_(result, ncr);
    } catch (const std::exception& ex) {
        LOG_ERROR(dctl_logger, DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR)
                  .arg(ex.what());
    }
84
85

    // Start the next IO layer asynchronous receive.
86
87
88
89
90
91
92
93
94
95
96
97
    // In the event the handler above intervened and decided to stop listening
    // we need to check that first.
    if (amListening()) {
        try {
            doReceive();
        } catch (const isc::Exception& ex) {
            // It is possible though unlikely, for doReceive to fail without
            // scheduling the read. While, unlikely, it does mean the callback
            // will not get called with a failure. A throw here would surface
            // at the IOService::run (or run variant) invocation.  So we will
            // close the window by invoking the application handler with
            // a failed result, and let the application layer sort it out.
98
99
100
101
102
103
104
            LOG_ERROR(dctl_logger, DHCP_DDNS_NCR_RECV_NEXT_ERROR)
                      .arg(ex.what());

            // Call the registered application layer handler.
            // Surround the invocation with a try-catch. The invoked handler is
            // not supposed to throw, but in the event it does we will at least
            // report it.
105
            NameChangeRequestPtr empty;
106
107
108
109
110
111
112
            try {
                recv_handler_(ERROR, empty);
            } catch (const std::exception& ex) {
                LOG_ERROR(dctl_logger,
                          DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR)
                          .arg(ex.what());
            }
113
114
115
        }
    }
}
116
117
118

//************************* NameChangeSender ******************************

119
120
NameChangeSender::NameChangeSender(RequestSendHandler& send_handler,
                                   size_t send_queue_max)
121
    : sending_(false), send_handler_(send_handler),
122
      send_queue_max_(send_queue_max) {
123
124

    // Queue size must be big enough to hold at least 1 entry.
125
126
    if (send_queue_max == 0) {
        isc_throw(NcrSenderError, "NameChangeSender constructor"
127
128
                  " queue size must be greater than zero");
    }
129
}
130
131

void
132
NameChangeSender::startSending(isc::asiolink::IOService& io_service) {
133
134
135
136
137
138
139
140
141
142
143
144
145
    if (amSending()) {
        // This amounts to a programmatic error.
        isc_throw(NcrSenderError, "NameChangeSender is already sending");
    }

    // Clear send marker.
    ncr_to_send_.reset();

    // Call implementation dependent open.
    try {
        open(io_service);
    } catch (const isc::Exception& ex) {
        stopSending();
146
        isc_throw(NcrSenderOpenError, "Open failed: " << ex.what());
147
148
149
150
151
152
153
154
155
156
157
158
159
160
    }

    // Set our status to sending.
    setSending(true);
}

void
NameChangeSender::stopSending() {
    try {
        // Call implementation dependent close.
        close();
    } catch (const isc::Exception &ex) {
        // Swallow exceptions. If we have some sort of error we'll log
        // it but we won't propagate the throw.
161
        LOG_ERROR(dctl_logger, DHCP_DDNS_NCR_SEND_CLOSE_ERROR).arg(ex.what());
162
    }
163
164
165

    // Set it false, no matter what.  This allows us to at least try to
    // re-open via startSending().
166
167
168
169
    setSending(false);
}

void
170
NameChangeSender::sendRequest(NameChangeRequestPtr& ncr) {
171
172
173
174
175
176
177
178
    if (!amSending()) {
        isc_throw(NcrSenderError, "sender is not ready to send");
    }

    if (!ncr) {
        isc_throw(NcrSenderError, "request to send is empty");
    }

179
180
181
    if (send_queue_.size() >= send_queue_max_) {
        isc_throw(NcrSenderQueueFull, "send queue has reached maximum capacity:"
                  << send_queue_max_ );
182
183
184
    }

    // Put it on the queue.
185
    send_queue_.push_back(ncr);
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201

    // Call sendNext to schedule the next one to go.
    sendNext();
}

void
NameChangeSender::sendNext() {
    if (ncr_to_send_) {
        // @todo Not sure if there is any risk of getting stuck here but
        // an interval timer to defend would be good.
        // In reality, the derivation should ensure they timeout themselves
        return;
    }

    // If queue isn't empty, then get one from the front. Note we leave
    // it on the front of the queue until we successfully send it.
202
    if (!send_queue_.empty()) {
203
        ncr_to_send_ = send_queue_.front();
204
205
206
207
208
209
210
211
212
213
214

       // @todo start defense timer
       // If a send were to hang and we timed it out, then timeout
       // handler need to cycle thru open/close ?

       // Call implementation dependent send.
       doSend(ncr_to_send_);
    }
}

void
215
NameChangeSender::invokeSendHandler(const NameChangeSender::Result result) {
216
217
218
    // @todo reset defense timer
    if (result == SUCCESS) {
        // It shipped so pull it off the queue.
219
        send_queue_.pop_front();
220
221
222
223
    }

    // Invoke the completion handler passing in the result and a pointer
    // the request involved.
224
225
226
227
228
229
230
231
232
    // Surround the invocation with a try-catch. The invoked handler is
    // not supposed to throw, but in the event it does we will at least
    // report it.
    try {
        send_handler_(result, ncr_to_send_);
    } catch (const std::exception& ex) {
        LOG_ERROR(dctl_logger, DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR)
                  .arg(ex.what());
    }
233
234
235
236
237

    // Clear the pending ncr pointer.
    ncr_to_send_.reset();

    // Set up the next send
238
239
240
241
242
243
244
245
246
    try {
        sendNext();
    } catch (const isc::Exception& ex) {
        // It is possible though unlikely, for sendNext to fail without
        // scheduling the send. While, unlikely, it does mean the callback
        // will not get called with a failure. A throw here would surface
        // at the IOService::run (or run variant) invocation.  So we will
        // close the window by invoking the application handler with
        // a failed result, and let the application layer sort it out.
247
248
249
250
251
252
253
254
255
256
257
258
259
        LOG_ERROR(dctl_logger, DHCP_DDNS_NCR_SEND_NEXT_ERROR)
                  .arg(ex.what());

        // Invoke the completion handler passing in failed result.
        // Surround the invocation with a try-catch. The invoked handler is
        // not supposed to throw, but in the event it does we will at least
        // report it.
        try {
            send_handler_(ERROR, ncr_to_send_);
        } catch (const std::exception& ex) {
            LOG_ERROR(dctl_logger, DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR)
                      .arg(ex.what());
        }
260
261
    }
}
262
263
264

void
NameChangeSender::skipNext() {
265
    if (!send_queue_.empty()) {
266
        // Discards the request at the front of the queue.
267
        send_queue_.pop_front();
268
269
270
271
    }
}

void
272
NameChangeSender::clearSendQueue() {
273
    if (amSending()) {
274
        isc_throw(NcrSenderError, "Cannot clear queue while sending");
275
276
    }

277
    send_queue_.clear();
278
279
280
281
}

} // namespace isc::d2
} // namespace isc