Logo Search packages:      
Sourcecode: xapian-core version File versions  Download package

remoteconnection.cc

Go to the documentation of this file.
/** @file  remoteconnection.cc
 *  @brief RemoteConnection class used by the remote backend.
 */
/* Copyright (C) 2006 Olly Betts
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 */

#include <config.h>

#include "safeerrno.h"

#include <string>

#include "omdebug.h"
#include "omtime.h"
#include "remoteconnection.h"
#include "serialise.h"

#ifdef HAVE_SYS_SELECT_H
# include <sys/select.h>
#else
# include <sys/time.h>
# include <sys/types.h>
# include <unistd.h>
#endif
#include <string.h> // Solaris needs this as FDSET uses memset but fails to prototype it.

RemoteConnection::RemoteConnection(int fdin_, int fdout_,
                           const string & context_)
    : fdin(fdin_), fdout(fdout_), context(context_)
{
}

void
00048 RemoteConnection::read_at_least(size_t min_len, const OmTime & end_time)
{
    DEBUGCALL(REMOTE, string, "RemoteConnection::read_at_least",
            min_len << ", " << end_time);

    if (buffer.length() >= min_len) return;

    // If there's no end_time, just use blocking I/O.
    if (fcntl(fdin, F_SETFL, end_time.is_set() ? O_NONBLOCK : 0) < 0) {
      throw Xapian::NetworkError("Failed to set fdin non-blocking-ness",
                           context, errno);
    }

    while (true) {
      char buf[4096];
      ssize_t received = read(fdin, buf, sizeof(buf));

      if (received > 0) {
          buffer.append(buf, buf + received);
          if (buffer.length() >= min_len) return;
          continue;
      }

      if (received == 0)
          throw Xapian::NetworkError("Received EOF", context, 0);

      DEBUGLINE(REMOTE, "read gave errno = " << strerror(errno));
      if (errno == EINTR) continue;

      if (errno != EAGAIN)
          throw Xapian::NetworkError("read failed", context, errno);

      Assert(end_time.is_set());
      while (true) {
          // Calculate how far in the future end_time is.
          OmTime time_diff = end_time - OmTime::now();
          // Check if the timeout has expired.
          if (time_diff.sec < 0) {
            DEBUGLINE(REMOTE, "read: timeout has expired");
            throw Xapian::NetworkTimeoutError("Timeout expired while trying to read", context);
          }

          struct timeval tv;
          tv.tv_sec = time_diff.sec;
          tv.tv_usec = time_diff.usec;

          // Use select to wait until there is data or the timeout is reached.
          fd_set fdset;
          FD_ZERO(&fdset);
          FD_SET(fdin, &fdset);

          int select_result = select(fdin + 1, &fdset, 0, &fdset, &tv);
          if (select_result > 0) break;

          if (select_result == 0)
            throw Xapian::NetworkTimeoutError("Timeout expired while trying to read", context);

          // EINTR means select was interrupted by a signal.
          if (errno != EINTR)
            throw Xapian::NetworkError("select failed during read", context, errno);
      }
    }
}

bool
00113 RemoteConnection::ready_to_read() const
{
    DEBUGCALL(REMOTE, bool, "RemoteConnection::ready_to_read", "");

    if (!buffer.empty()) RETURN(true);

    // Use select to see if there's data available to be read.
    fd_set fdset;
    FD_ZERO(&fdset);
    FD_SET(fdin, &fdset);

    // Set a 0.1 second timeout to avoid a busy loop.
    // FIXME: this would be much better done by exposing the fd so that the
    // matcher can call select on all the fds involved...
    struct timeval tv;
    tv.tv_sec = 0;
    tv.tv_usec = 100000;
    RETURN(select(fdin + 1, &fdset, 0, &fdset, &tv) > 0);
}

void
RemoteConnection::send_message(char type, const string &message, const OmTime & end_time)
{
    DEBUGCALL(REMOTE, void, "RemoteConnection::send_message",
            type << ", " << message << ", " << end_time);

    // If there's no end_time, just use blocking I/O.
    if (fcntl(fdin, F_SETFL, end_time.is_set() ? O_NONBLOCK : 0) < 0) {
      throw Xapian::NetworkError("Failed to set fdout non-blocking-ness",
                           context, errno);
    }

    string header;
    header += type;
    header += encode_length(message.size());

    const string * str = &header;

    fd_set fdset;
    size_t count = 0;
    while (true) {
      // We've set write to non-blocking, so just try writing as there
      // will usually be space.
      ssize_t n = write(fdout, str->data() + count, str->size() - count);

      if (n >= 0) {
          count += n;
          if (count == str->size()) {
            if (str == &message || message.empty()) break;
            str = &message;
            count = 0;
          }
          continue;
      }

      DEBUGLINE(REMOTE, "write gave errno = " << strerror(errno));
      if (errno == EINTR) continue;

      if (errno != EAGAIN)
          throw Xapian::NetworkError("write failed", context, errno);

      // Use select to wait until there is space or the timeout is reached.
      FD_ZERO(&fdset);
      FD_SET(fdout, &fdset);

      OmTime time_diff(end_time - OmTime::now());
      if (time_diff.sec < 0) {
          DEBUGLINE(REMOTE, "write: timeout has expired");
          throw Xapian::NetworkTimeoutError("Timeout expired while trying to write", context);
      }

      struct timeval tv;
      tv.tv_sec = time_diff.sec;
      tv.tv_usec = time_diff.usec;

      int select_result = select(fdout + 1, 0, &fdset, &fdset, &tv);

      if (select_result < 0) {
          if (errno == EINTR) {
            // EINTR means select was interrupted by a signal.
            // We could just retry the select, but it's easier to just
            // retry the write.
            continue;
          }
          throw Xapian::NetworkError("select failed during write", context, errno);
      }

      if (select_result == 0)
          throw Xapian::NetworkTimeoutError("Timeout expired while trying to write", context);
    }
}

char
RemoteConnection::get_message(string &result, const OmTime & end_time)
{
    DEBUGCALL(REMOTE, char, "RemoteConnection::get_message",
            "[result], " << end_time);

    read_at_least(2, end_time);
    size_t len = static_cast<unsigned char>(buffer[1]);
    read_at_least(len + 2, end_time);
    if (len != 0xff) {
      result.assign(buffer.data() + 2, len);
      char type = buffer[0];
      buffer.erase(0, len + 2);
      RETURN(type);
    }
    len = 0;
    string::const_iterator i = buffer.begin() + 2;
    unsigned char ch;
    int shift = 0;
    do {
      if (i == buffer.end() || shift > 28) {
          // Something is very wrong...
          throw Xapian::InternalError("Insane length specified!");
      }
      ch = *i++;
      len |= size_t(ch & 0x7f) << shift;
      shift += 7;
    } while ((ch & 0x80) == 0);
    len += 255;
    size_t header_len = (i - buffer.begin());
    read_at_least(header_len + len, end_time);
    result.assign(buffer.data() + header_len, len);
    char type = buffer[0];
    buffer.erase(0, header_len + len);
    RETURN(type);
}

void
00243 RemoteConnection::do_close()
{
    DEBUGCALL(REMOTE, void, "RemoteConnection::do_close", "");

    if (fdout == -1) return;
    // We can be called from a destructor, so we can't throw an exception.
    try {
      /* If we can't send the close-down message right away, then just
       * close the connection as the other end will cope.
       */
      send_message(MSG_SHUTDOWN, "", OmTime::now());
    } catch (...) {
    }
    close(fdin);
    if (fdin != fdout) close(fdout);
    fdout = -1;
}

Generated by  Doxygen 1.6.0   Back to index