Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add socket injection into client #147

Merged
merged 11 commits into from
Feb 15, 2022

Conversation

itrofimow
Copy link
Contributor

@itrofimow itrofimow commented Feb 2, 2022

These changes unveil the potential of the library for asynchronous applications (namely, web-servers) where blocking IO is not desirable.

The ability to inject some async-based communication channel should also ease creation of bindings for languages/technologies that strive for c++ performance but cant allow blocking IO (python, Node.js, etc..)

Submitted on behalf of a third-party: Yandex N.V.

@CLAassistant
Copy link

CLAassistant commented Feb 2, 2022

CLA assistant check
All committers have signed the CLA.

Copy link
Collaborator

@Enmk Enmk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea and motivation is quite clear. But current implementation has problems with re-connecting. IMO to address this, it would make sense to have some sort of SocketFactory in addition to SocketBase, please consider the following interface:

class SocketFactory
{
    virtual ~SocketFactory();
    virtual std::unique_ptr<SocketBase> connect() = 0;
};

And two implementors of this interface:
NonSecureSocketFactory and TLSSocketFactory, which handle everything related to establishing connection.

@itrofimow
Copy link
Contributor Author

I thought about that, but another problem arises - re-connection mechanism uses blocking this_thread::sleep_for, that kinda defies purpose of this PR.

There might be a workaroung in the lines of SetCustomSleepImpl with this_thread::sleep_for being default implementation, but i didn't want to mix it together in one PR.

What you propose definitely makes sense, but for now would require opts.send_retries = 0 to avoid blocking

@itrofimow
Copy link
Contributor Author

itrofimow commented Feb 2, 2022

Oh, may be i misunderstood you and you propose to move whole connecting + reconnecting logic to those factories? @Enmk

@itrofimow itrofimow requested a review from Enmk February 3, 2022 22:32
#if defined(WITH_OPENSSL)
std::unique_ptr<Socket> SSLSocketFactory::doConnect(const ClientOptions& opts,
const NetworkAddress& address) {
std::unique_ptr<SSLContext> ssl_context;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO it would make sense to store SSLContext in a SSLSocketFactory and create it only once: in constructor.

public:
~SSLSocketFactory() override;

#if defined(WITH_OPENSSL)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whole file is not event visible to the build-system, if WITH_OPENSSL is not defined. Please remove the #if defined/#endif braces.

};


class SocketFactory {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To simplify issue with reconnecting and timeouts (and to provide a framework for upcoming multiple-hosts #139 ). Please consider updating the interface:

class SocketFactory {
        public:
    virtual ~SocketFactory();

    // TODO: move connection-related options to ConnectionOptions structure.

    virtual std::unique_ptr<SocketBase> Connect(const NetworkAddress &, const ClientOptions &) = 0;
    // reconnects to a given address, maybe with a timeout or with any special procedures.
    virtual std::unique_ptr<SocketBase> ReConnect(const NetworkAddress &, const ClientOptions &) = 0;
};

Copy link
Contributor Author

@itrofimow itrofimow Feb 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a problem with NetworkAddress - its constructor uses blocking getaddrinfo; one way of soling this is to make some base class for NetworkAddress, but i feel like this would complicate the interface too much (and we already have some Java-style abstract factories here)

Introducing ConnectionOptions seems like a good enhancement for me, but i believe it has to be done in #139, or at least on the receiving end of upcoming merge conflict. Since SocketFactory is a new feature, there shouldn't be a concern with backwards compatibility

Copy link
Collaborator

@Enmk Enmk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks better, but please consider removing SleepImpl - too complicated and error-prone.
The way the Client sleeps before reconnecting is an implementation detail of the reconnection procedure. Hence it should be moved into SockerFactory.

Also, some other modifications to the SocketFactory interface are required to simplify setting multiple hosts (see #139 ).

return std::make_unique<NonSecureSocketFactory>();
}

std::unique_ptr<SleepImpl> GetDefaultSleepImpl() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please consider changing the interface to get rid of the SleepImpl
https://github.com/ClickHouse/clickhouse-cpp/pull/147/files#r799244776

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on this

IMO, sleeping in a specific manner is still an implementation detail bound to the underlying transport mechanism

i did move sleepFor into SocketFactory

@Enmk
Copy link
Collaborator

Enmk commented Feb 4, 2022

Looks better, but please consider removing SleepImpl - too complicated and error-prone. The way the Client sleeps before reconnecting is an implementation detail of the reconnection procedure. Hence it should be moved into SockerFactory.

Also, some other modifications to the SocketFactory interface are required to simplify setting multiple hosts (see #139 ).

Please note that you are not required to implement multiple host configuration, just bear it in mind.

@itrofimow
Copy link
Contributor Author

The way the Client sleeps before reconnecting is an implementation detail of the reconnection procedure. Hence it should be moved into SockerFactory.

It is indeed the detail of reconnection procedure, however said procedure not only just creates a socket, but also initializes some streams over it, handshakes, sleeps, retries and what not (might do more logic as in #139). All of that could be moved to some other class (say, ConnectionFactory), but that:

  1. would require a lot of refactoring for decoupling from Client::Impl
  2. doesn't seem to be something Client is willing to accept from outside

What i am saying is that i'm not sure if SocketFactory is the one responsible for implementing Sleep and Client::Impl::RetryGuard kinda proves the point - it sleeps before resetting connection, following its own logic.

Moving Sleep to SocketFactory solves blocking problem for now, but i'm afraid some other peaces of code, possibly completely unrelated to sockets, would decide to sleep and just throw this_thread::sleep_for in, breaking the library for me


SleepImpl - too complicated and error-prone

Could you please elaborate on that, what exactly you find error-prone?


And about #139 - maybe out conflicting logic could be resolved together with the actual merge (and there would be a merge conflict for sure)?
@Enmk

@Enmk
Copy link
Collaborator

Enmk commented Feb 7, 2022

@itrofimow please see answers inline

What i am saying is that i'm not sure if SocketFactory is the one responsible for implementing Sleep and Client::Impl::RetryGuard kinda proves the point - it sleeps before resetting connection, following its own logic.

IMO, sleeping in a specific manner is still an implementation detail bound to the underlying transport mechanism, so separating those may bring more harm than good.

Moving Sleep to SocketFactory solves blocking problem for now, but i'm afraid some other peaces of code, possibly completely unrelated to sockets, would decide to sleep and just throw this_thread::sleep_for in, breaking the library for me

I think we should ban sleeping in any other piece of code.

Could you please elaborate on that, what exactly you find error-prone?

One has to keep two things in-sync, paying close attention that SleepImpl and SocketFactory implementations are compatible. So with SleepImpl there are 4 combinations:

  • Default SleepImpl + Default SocketFactory
  • Custom SleepImpl + Custom SocketFactory
  • Default SleepImpl + Custom SocketFactory
  • Custom SleepImpl + Default SocketFactory

What's even worse, most of the time, any combination might seem to work (since sleeper is not used in the good-path). But may break in a weird way in some edge case. And networking code is hard to test for edge cases.

@itrofimow
Copy link
Contributor Author

@Enmk please have a look at my inline answers

One could argue that sleeping in a specific manner isn't necessary bound to the underlying transports async nature, and that it's the caller who is responsible for keeping provided SocketFactory and SleepImpl in sync, but if you ban sleeping in any other piece of code that would do just fine for me - removed SleepImpl

@itrofimow itrofimow requested a review from Enmk February 8, 2022 13:49
@itrofimow
Copy link
Contributor Author

@Enmk kindly re requesting review in case this PR got lost a bit

Copy link
Collaborator

@Enmk Enmk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Enmk Enmk merged commit 58ef332 into ClickHouse:master Feb 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants