sapling/eden/fs/utils/FutureUnixSocket.h
Wez Furlong be130bc880 add version handshake to takeover protocol
Summary:
Whilst chatting with simpkins we realized that we lost
the handshake portion of the takeover protocol during a refactor.

The handshake is important for a couple of reasons:

1. It prevents unmounting and loosing all the mounts in the case
   that sometime decides to netcat or otherwise connect to the
   socket
2. It gives us an opportunity to short circuit any heavy lifting
   if we know that it will be impossible to succeed.
3. It allows us to rollback to earlier builds with older versions
   of the takeover protocol.

This diff adds a little bit of machinery to enable passing a set of supported
takeover protocol version numbers.  The intent is to retain support for
the two of these at a time; any time we change the encoding/protocol
for takeover we'll bump the version number and add supporting code
to handle the new format, retaining support for the prior version.

Retaining the ability to handle the prior version allows us to downgrade
to an earlier build gracefully if/when the need arises.

I opted to do this here rather than by bumping the `kProtocolID`
constant in `UnixSocket.h` becase we're not really changing the
lowest level of the protocol; just the takeover specific portions.

I haven't actually changed the takeover serialization in this diff,
but do have some work on that happening in D6733406; that diff will
be amended to take advantage and demonstrate how this versioning
scheme works.

A key thing to note about the implementation of this diff is that
the client sends the version number to the server, but doesn't
add any explicit version encoding in the response we receive.
This is deliberate and allows us to upgrade prior builds to
this new scheme.  I'll add a more definitive check for this
situation when I actually rev the format in the following diff.

Reviewed By: simpkins

Differential Revision: D6743065

fbshipit-source-id: c991cebfee918daad098105ca6bcfef76374c0ff
2018-01-30 14:21:18 -08:00

142 lines
4.0 KiB
C++

/*
* Copyright (c) 2004-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#pragma once
#include <folly/futures/Future.h>
#include "eden/fs/utils/UnixSocket.h"
namespace facebook {
namespace eden {
/**
* A wrapper around UnixSocket that provides a Future-based API
* rather than raw callback objects.
*
* This class is not thread safe. It should only be accessed from the
* EventBase thread that it is attached to.
*/
class FutureUnixSocket : private UnixSocket::ReceiveCallback {
public:
using Message = UnixSocket::Message;
/**
* Create a new unconnected FutureUnixSocket object.
*
* connect() should be called on this socket before any other I/O operations.
*/
FutureUnixSocket();
/**
* Create a FutureUnixSocket object from an existing UnixSocket.
*/
explicit FutureUnixSocket(UnixSocket::UniquePtr socket);
/**
* Create a FutureUnixSocket object from an existing socket descriptor.
*/
FutureUnixSocket(folly::EventBase* eventBase, folly::File socket);
~FutureUnixSocket();
FutureUnixSocket(FutureUnixSocket&& other) noexcept;
FutureUnixSocket& operator=(FutureUnixSocket&& other) noexcept;
/**
* Connect to a unix socket.
*/
folly::Future<folly::Unit> connect(
folly::EventBase* eventBase,
const folly::SocketAddress& address,
std::chrono::milliseconds timeout);
folly::Future<folly::Unit> connect(
folly::EventBase* eventBase,
folly::StringPiece path,
std::chrono::milliseconds timeout);
/**
* Get the EventBase that this socket uses for driving I/O operations.
*
* All interaction with this FutureUnixSocket object must be done from this
* EventBase's thread.
*/
folly::EventBase* getEventBase() const {
return socket_->getEventBase();
}
void setSendTimeout(std::chrono::milliseconds timeout) {
return socket_->setSendTimeout(timeout);
}
/**
* Returns 'true' if the underlying descriptor is open, or rather,
* it has not been closed locally.
*/
explicit operator bool() const {
return socket_.get() != nullptr;
}
/**
* Get the user ID of the remote peer.
*/
uid_t getRemoteUID();
/**
* Send a message.
*
* Returns a Future that will complete when the message has been handed off
* to the kernel for delivery.
*/
folly::Future<folly::Unit> send(Message&& msg);
folly::Future<folly::Unit> send(folly::IOBuf&& data) {
return send(Message(std::move(data)));
}
folly::Future<folly::Unit> send(std::unique_ptr<folly::IOBuf> data) {
return send(Message(std::move(*data)));
}
/**
* Receive a message.
*
* Returns a Future that will be fulfilled when a message is received.
*
* receive() may be called multiple times in a row without waiting for
* earlier receive() calls to be fulfilled. In this case the futures will be
* fulfilled as messages are received in the order in which they were
* created. (The first receive() call will receive the first message
* received on the socket, the second receive() call will receive the second
* message, etc.)
*/
folly::Future<Message> receive(std::chrono::milliseconds timeout);
private:
class SendCallback;
class ReceiveCallback;
class ConnectCallback;
void receiveTimeout();
void messageReceived(Message&& message) noexcept override;
void eofReceived() noexcept override;
void socketClosed() noexcept override;
void receiveError(const folly::exception_wrapper& ew) noexcept override;
void failAllPromises(const folly::exception_wrapper& error) noexcept;
static void failReceiveQueue(
std::unique_ptr<ReceiveCallback> callback,
const folly::exception_wrapper& ew);
UnixSocket::UniquePtr socket_;
std::unique_ptr<ReceiveCallback> recvQueue_;
ReceiveCallback* recvQueueTail_{nullptr};
};
} // namespace eden
} // namespace facebook