root / tags / 1.0.8 / xapian-core / net / remoteconnection.cc

Revision 10762, 13.4 kB (checked in by olly, 6 months ago)

Backport change from trunk:
net/remoteconnection.cc: Shrink the try block to only cover the call
to send_message().

  • Property svn:eol-style set to native
Line 
1/** @file  remoteconnection.cc
2 *  @brief RemoteConnection class used by the remote backend.
3 */
4/* Copyright (C) 2006,2007 Olly Betts
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19 */
20
21#include <config.h>
22
23#include <xapian/error.h>
24
25#include "safeerrno.h"
26#include "safefcntl.h"
27#include "safeunistd.h"
28
29#include <string>
30
31#include "omassert.h"
32#include "omdebug.h"
33#include "omtime.h"
34#include "remoteconnection.h"
35#include "serialise.h"
36
37#ifndef __WIN32__
38# include "safesysselect.h"
39#endif
40
41using namespace std;
42
43#ifdef __WIN32__
44// __STDC_SECURE_LIB__ doesn't appear to be publicly documented, but appears
45// to be a good idea.  We cribbed this test from the python sources - see, for
46// example, http://svn.python.org/view?rev=47223&view=rev
47# if defined _MSC_VER && _MSC_VER >= 1400 && defined __STDC_SECURE_LIB__
48include <stdlib.h> // For _set_invalid_parameter_handler(), etc.
49include <crtdbg.h> // For _CrtSetReportMode, etc.
50
51/** A dummy invalid parameter handler which ignores the error. */
52static void dummy_handler(const wchar_t*,
53                          const wchar_t*,
54                          const wchar_t*,
55                          unsigned int,
56                          uintptr_t)
57{
58}
59
60// Recent versions of MSVC call an "_invalid_parameter_handler" if a
61// CRT function receives an invalid parameter.  However, there are cases
62// where this is totally reasonable.  To avoid the application dying,
63// you just need to instantiate the MSVCIgnoreInvalidParameter class in
64// the scope where you want MSVC to ignore invalid parameters.
65class MSVCIgnoreInvalidParameter {
66    _invalid_parameter_handler old_handler;
67    int old_report_mode;
68
69  public:
70    MSVCIgnoreInvalidParameter() {
71        // Install a dummy handler to avoid the program dying.
72        old_handler = _set_invalid_parameter_handler(dummy_handler);
73        // Make sure that no dialog boxes appear.
74        old_report_mode = _CrtSetReportMode(_CRT_ASSERT, 0);
75    }
76
77    ~MSVCIgnoreInvalidParameter() {
78        // Restore the previous settings.
79        _set_invalid_parameter_handler(old_handler);
80        _CrtSetReportMode(_CRT_ASSERT, old_report_mode);
81    }
82};
83# else
84// Mingw seems to be free of this insanity, so for this and older MSVC versions
85// define a dummy class to allow MSVCIgnoreInvalidParameter to be used
86// unconditionally.
87struct MSVCIgnoreInvalidParameter {
88    // Provide an explicit constructor so this isn't a POD struct - this seems
89    // to prevent GCC warning about an unused variable whenever we instantiate
90    // this class.
91    MSVCIgnoreInvalidParameter() { }
92};
93# endif
94
95/// Convert an fd (which might be a socket) to a WIN32 HANDLE.
96static HANDLE fd_to_handle(int fd) {
97    MSVCIgnoreInvalidParameter invalid_handle_value_is_ok;
98    HANDLE handle = (HANDLE)_get_osfhandle(fd);
99    // On WIN32, a socket fd isn't the same as a non-socket fd - in fact
100    // it's already a HANDLE!
101    return (handle != INVALID_HANDLE_VALUE ? handle : (HANDLE)fd);
102}
103
104/// Close an fd, which might be a socket.
105static void close_fd_or_socket(int fd) {
106    MSVCIgnoreInvalidParameter invalid_fd_value_is_ok;
107    if (close(fd) == -1 && errno == EBADF) {
108        // Bad file descriptor - probably because the fd is actually
109        // a socket.
110        closesocket(fd);
111    }
112}
113#else
114// There's no distinction between sockets and other fds on UNIX.
115inline void close_fd_or_socket(int fd) { close(fd); }
116#endif
117
118RemoteConnection::RemoteConnection(int fdin_, int fdout_,
119                                   const string & context_)
120    : fdin(fdin_), fdout(fdout_), context(context_)
121{
122#ifdef __WIN32__
123    memset(&overlapped, 0, sizeof(overlapped));
124    overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
125    if (!overlapped.hEvent)
126        throw Xapian::NetworkError("Failed to setup OVERLAPPED",
127                                   context, -(int)GetLastError());
128
129#endif
130}
131
132RemoteConnection::~RemoteConnection()
133{
134#ifdef __WIN32__
135    if (overlapped.hEvent)
136        CloseHandle(overlapped.hEvent);
137#endif
138}
139
140void
141RemoteConnection::read_at_least(size_t min_len, const OmTime & end_time)
142{
143    DEBUGCALL(REMOTE, string, "RemoteConnection::read_at_least",
144              min_len << ", " << end_time);
145
146    if (buffer.length() >= min_len) return;
147
148#ifdef __WIN32__
149    HANDLE hin = fd_to_handle(fdin);
150    do {
151        char buf[4096];
152        DWORD received;
153        BOOL ok = ReadFile(hin, buf, sizeof(buf), &received, &overlapped);
154        if (!ok) {
155            int errcode = GetLastError();
156            if (errcode != ERROR_IO_PENDING)
157                throw Xapian::NetworkError("read failed", context, -errcode);
158            // Is asynch - just wait for the data to be received or a timeout.
159            DWORD waitrc;
160            waitrc = WaitForSingleObject(overlapped.hEvent, calc_read_wait_msecs(end_time));
161            if (waitrc != WAIT_OBJECT_0) {
162                DEBUGLINE(REMOTE, "read: timeout has expired");
163                throw Xapian::NetworkTimeoutError("Timeout expired while trying to read", context);
164            }
165            // Get the final result of the read.
166            if (!GetOverlappedResult(hin, &overlapped, &received, FALSE))
167                throw Xapian::NetworkError("Failed to get overlapped result",
168                                           context, -(int)GetLastError());
169        }
170
171        if (received == 0)
172            throw Xapian::NetworkError("Received EOF", context);
173
174        buffer.append(buf, received);
175    } while (buffer.length() < min_len);
176#else
177    // If there's no end_time, just use blocking I/O.
178    if (fcntl(fdin, F_SETFL, end_time.is_set() ? O_NONBLOCK : 0) < 0) {
179        throw Xapian::NetworkError("Failed to set fdin non-blocking-ness",
180                                   context, errno);
181    }
182
183    while (true) {
184        char buf[4096];
185        ssize_t received = read(fdin, buf, sizeof(buf));
186
187        if (received > 0) {
188            buffer.append(buf, received);
189            if (buffer.length() >= min_len) return;
190            continue;
191        }
192
193        if (received == 0)
194            throw Xapian::NetworkError("Received EOF", context);
195
196        DEBUGLINE(REMOTE, "read gave errno = " << strerror(errno));
197        if (errno == EINTR) continue;
198
199        if (errno != EAGAIN)
200            throw Xapian::NetworkError("read failed", context, errno);
201
202        Assert(end_time.is_set());
203        while (true) {
204            // Calculate how far in the future end_time is.
205            OmTime time_diff = end_time - OmTime::now();
206            // Check if the timeout has expired.
207            if (time_diff.sec < 0) {
208                DEBUGLINE(REMOTE, "read: timeout has expired");
209                throw Xapian::NetworkTimeoutError("Timeout expired while trying to read", context);
210            }
211
212            struct timeval tv;
213            tv.tv_sec = time_diff.sec;
214            tv.tv_usec = time_diff.usec;
215
216            // Use select to wait until there is data or the timeout is reached.
217            fd_set fdset;
218            FD_ZERO(&fdset);
219            FD_SET(fdin, &fdset);
220
221            int select_result = select(fdin + 1, &fdset, 0, &fdset, &tv);
222            if (select_result > 0) break;
223
224            if (select_result == 0)
225                throw Xapian::NetworkTimeoutError("Timeout expired while trying to read", context);
226
227            // EINTR means select was interrupted by a signal.
228            if (errno != EINTR)
229                throw Xapian::NetworkError("select failed during read", context, errno);
230        }
231    }
232#endif
233}
234
235bool
236RemoteConnection::ready_to_read() const
237{
238    DEBUGCALL(REMOTE, bool, "RemoteConnection::ready_to_read", "");
239
240    if (!buffer.empty()) RETURN(true);
241
242    // Use select to see if there's data available to be read.
243    fd_set fdset;
244    FD_ZERO(&fdset);
245    FD_SET(fdin, &fdset);
246
247    // Set a 0.1 second timeout to avoid a busy loop.
248    // FIXME: this would be much better done by exposing the fd so that the
249    // matcher can call select on all the fds involved...
250    struct timeval tv;
251    tv.tv_sec = 0;
252    tv.tv_usec = 100000;
253    RETURN(select(fdin + 1, &fdset, 0, &fdset, &tv) > 0);
254}
255
256void
257RemoteConnection::send_message(char type, const string &message, const OmTime & end_time)
258{
259    DEBUGCALL(REMOTE, void, "RemoteConnection::send_message",
260              type << ", " << message << ", " << end_time);
261
262    string header;
263    header += type;
264    header += encode_length(message.size());
265
266#ifdef __WIN32__
267    HANDLE hout = fd_to_handle(fdout);
268    const string * str = &header;
269
270    size_t count = 0;
271    while (true) {
272        DWORD n;
273        BOOL ok = WriteFile(hout, str->data() + count, str->size() - count, &n, &overlapped);
274        if (!ok) {
275            int errcode = GetLastError();
276            if (errcode != ERROR_IO_PENDING)
277                throw Xapian::NetworkError("write failed", context, -errcode);
278            // Just wait for the data to be received, or a timeout.
279            DWORD waitrc;
280            waitrc = WaitForSingleObject(overlapped.hEvent, calc_read_wait_msecs(end_time));
281            if (waitrc != WAIT_OBJECT_0) {
282                DEBUGLINE(REMOTE, "write: timeout has expired");
283                throw Xapian::NetworkTimeoutError("Timeout expired while trying to write", context);
284            }
285            // Get the final result.
286            if (!GetOverlappedResult(hout, &overlapped, &n, FALSE))
287                throw Xapian::NetworkError("Failed to get overlapped result",
288                                           context, -(int)GetLastError());
289        }
290
291        count += n;
292        if (count == str->size()) {
293            if (str == &message || message.empty()) return;
294            str = &message;
295            count = 0;
296        }
297    }
298#else
299    // If there's no end_time, just use blocking I/O.
300    if (fcntl(fdout, F_SETFL, end_time.is_set() ? O_NONBLOCK : 0) < 0) {
301        throw Xapian::NetworkError("Failed to set fdout non-blocking-ness",
302                                   context, errno);
303    }
304
305    const string * str = &header;
306
307    fd_set fdset;
308    size_t count = 0;
309    while (true) {
310        // We've set write to non-blocking, so just try writing as there
311        // will usually be space.
312        ssize_t n = write(fdout, str->data() + count, str->size() - count);
313
314        if (n >= 0) {
315            count += n;
316            if (count == str->size()) {
317                if (str == &message || message.empty()) return;
318                str = &message;
319                count = 0;
320            }
321            continue;
322        }
323
324        DEBUGLINE(REMOTE, "write gave errno = " << strerror(errno));
325        if (errno == EINTR) continue;
326
327        if (errno != EAGAIN)
328            throw Xapian::NetworkError("write failed", context, errno);
329
330        // Use select to wait until there is space or the timeout is reached.
331        FD_ZERO(&fdset);
332        FD_SET(fdout, &fdset);
333
334        OmTime time_diff(end_time - OmTime::now());
335        if (time_diff.sec < 0) {
336            DEBUGLINE(REMOTE, "write: timeout has expired");
337            throw Xapian::NetworkTimeoutError("Timeout expired while trying to write", context);
338        }
339
340        struct timeval tv;
341        tv.tv_sec = time_diff.sec;
342        tv.tv_usec = time_diff.usec;
343
344        int select_result = select(fdout + 1, 0, &fdset, &fdset, &tv);
345
346        if (select_result < 0) {
347            if (errno == EINTR) {
348                // EINTR means select was interrupted by a signal.
349                // We could just retry the select, but it's easier to just
350                // retry the write.
351                continue;
352            }
353            throw Xapian::NetworkError("select failed during write", context, errno);
354        }
355
356        if (select_result == 0)
357            throw Xapian::NetworkTimeoutError("Timeout expired while trying to write", context);
358    }
359#endif
360}
361
362char
363RemoteConnection::get_message(string &result, const OmTime & end_time)
364{
365    DEBUGCALL(REMOTE, char, "RemoteConnection::get_message",
366              "[result], " << end_time);
367
368    read_at_least(2, end_time);
369    size_t len = static_cast<unsigned char>(buffer[1]);
370    read_at_least(len + 2, end_time);
371    if (len != 0xff) {
372        result.assign(buffer.data() + 2, len);
373        char type = buffer[0];
374        buffer.erase(0, len + 2);
375        RETURN(type);
376    }
377    len = 0;
378    string::const_iterator i = buffer.begin() + 2;
379    unsigned char ch;
380    int shift = 0;
381    do {
382        if (i == buffer.end() || shift > 28) {
383            // Something is very wrong...
384            throw Xapian::NetworkError("Insane message length specified!");
385        }
386        ch = *i++;
387        len |= size_t(ch & 0x7f) << shift;
388        shift += 7;
389    } while ((ch & 0x80) == 0);
390    len += 255;
391    size_t header_len = (i - buffer.begin());
392    read_at_least(header_len + len, end_time);
393    result.assign(buffer.data() + header_len, len);
394    char type = buffer[0];
395    buffer.erase(0, header_len + len);
396    RETURN(type);
397}
398
399void
400RemoteConnection::do_close(bool wait)
401{
402    DEBUGCALL(REMOTE, void, "RemoteConnection::do_close", wait);
403
404    if (fdout == -1) return;
405    // We can be called from a destructor, so we can't throw an exception.
406    if (wait) {
407        try {
408            send_message(MSG_SHUTDOWN, string(), OmTime());
409        } catch (...) {
410        }
411#ifdef __WIN32__
412        HANDLE hin = fd_to_handle(fdin);
413        char dummy;
414        DWORD received;
415        BOOL ok = ReadFile(hin, &dummy, 1, &received, &overlapped);
416        if (!ok && GetLastError() == ERROR_IO_PENDING) {
417            // Wait for asynchronous read to complete.
418            (void)WaitForSingleObject(overlapped.hEvent, INFINITE);
419        }
420#else
421        // Wait for the connection to be closed - when this happens
422        // select() will report that a read won't block.
423        fd_set fdset;
424        FD_ZERO(&fdset);
425        FD_SET(fdin, &fdset);
426        int res;
427        do {
428            res = select(fdin + 1, &fdset, 0, &fdset, NULL);
429        } while (res < 0 && errno == EINTR);
430#endif
431    }
432    close_fd_or_socket(fdin);
433    if (fdin != fdout) close_fd_or_socket(fdout);
434    fdout = -1;
435}
436
437#ifdef __WIN32__
438DWORD
439RemoteConnection::calc_read_wait_msecs(const OmTime & end_time)
440{
441    if (!end_time.is_set())
442        return INFINITE;
443
444    // Calculate how far in the future end_time is.
445    OmTime now(OmTime::now());
446
447    DWORD msecs;
448
449    // msecs is unsigned, so we mustn't try and return a negative value
450    if (now > end_time) {
451        throw Xapian::NetworkTimeoutError("Timeout expired before starting read", context);
452    }
453    OmTime time_diff = end_time - now;
454    msecs = time_diff.sec * 1000 + time_diff.usec / 1000;
455    return msecs;
456}
457#endif
Note: See TracBrowser for help on using the browser.