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

Client/server (req-rep) specification #213

Merged
merged 3 commits into from
Jun 29, 2017
Merged

Client/server (req-rep) specification #213

merged 3 commits into from
Jun 29, 2017

Conversation

eile
Copy link
Contributor

@eile eile commented Jun 6, 2017

No description provided.

@eile eile force-pushed the master branch 2 times, most recently from 31d680f to 199bfc2 Compare June 6, 2017 12:37
@eile
Copy link
Contributor Author

eile commented Jun 7, 2017

See latest diff for a variant of asynchronous requests. The zmq API (req-rep-dealer-router) and doc is "flexible" for this type of use cases. I hope I got it now for the impl.

Copy link

@rdumusc rdumusc left a comment

Choose a reason for hiding this comment

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

Some general thoughts:

  • There is an apparent contradiction between "Certain operations are by necessity synchronous" and "Requirements: Asynchronous remote procedure call".
  • If the API is asynchronous it must return a future so that users can wait on the request completion.

I see the following use cases (requirements?) to shape the API:

  1. get a remote object, no parameters. This is the most trivial and common use case, such as the livreGUI connecting to a renderer and requesting initial state.

  2. get a remote object from a collection, with parameters passing for "filtering/selection", such as getting a specific block in the livre::RemoteDataSource example here or a group of morphologies from a circuit.

  3. set a remote object directly, i.e. without requiring that the server has "subscribed" to the client.

  4. basic RPC (execute a remote function) with or without return value and with or without parameters. It's easy enough to do basic RPC but it quickly becomes a complicated topic with future chaining, etc, better addressed by frameworks such as https://capnproto.org/rpc.html or HPX.

      1. are the equivalent of remote getters-setters for a property, and PUB-SUB can form the equivalent of notifyChanged signals.

Relationship with http::Server:

  1. and 2) are like handle(GET) in http::Server. 3) is like the handle(PUT) in http server. 4) doesn't have an exact http equivalent.

@eile
Copy link
Contributor Author

eile commented Jun 7, 2017

There is an apparent contradiction between "Certain operations are by necessity synchronous" and "Requirements: Asynchronous remote procedure call".

The exchange is synchronous, the API is not. Will clarify.

@eile
Copy link
Contributor Author

eile commented Jun 7, 2017

If the API is asynchronous it must return a future

No, it can also take a functor which is called on completion. I picked this approach since it does not require the reply object (if any) to be passed into the get().

@eile
Copy link
Contributor Author

eile commented Jun 7, 2017

I'll pick your use cases into the Examples section.

@eile
Copy link
Contributor Author

eile commented Jun 7, 2017

On reading them, maybe not: 1, 2 and 3 are special cases of 4:

  1. is 4 with a return value
  2. is 4 with parameters and a return value
  3. is 4 with parameters

@eile
Copy link
Contributor Author

eile commented Jun 7, 2017

Or, to phrase it differently: The get Serializable is both your function name (the type uuid) and parameters (object data, may be empty), and the ReplyFunc is your return value. With this you can do any remote call.


using ReplyFunc = std::function<void(const void*, size_t)>;

class Client : public Receiver
Copy link

Choose a reason for hiding this comment

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

Is the inheritance from Receiver important from the user's point of view or is it just convenient for the implementation? If it's the latter replace it with implementetion-defined in the spec.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it is important: The user can call a single receive() on a set of publishers, http::servers, clients and servers.


## API

using ReplyFunc = std::function<void(const void*, size_t)>;
Copy link

Choose a reason for hiding this comment

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

Refresh my memory, do Zerobuf objects check the type ID during deserialization? Otherwise we are lacking type safety here, however I don't have a good suggestion to fix it (except add the type check in Zerobuf).
In any case, if the server doesn't use Zerobuf or some other typesafe serialization engine, we are in trouble.


### 2: Is the client a receiver?

_Resolution: Yes:_ The get method only sends the request, the reply is received in receive() and correlated to the request.
Copy link

Choose a reason for hiding this comment

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

If a client issues millions of requests and doesn't call receive at all, what does it happen on the server?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Implementation-specific. zmq has high-water marks on pending requests, in this case the server reply will block eventually.

Copy link

Choose a reason for hiding this comment

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

But can the server block in a single reply if the client doesn't process it? Or the behaviour in this case is a socket parameter?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's a socket parameter, and afaik a single reply won't block.

class Client : public Receiver
{
// ctors same as Subscriber
void get(const Serializable& request, ReplyFunc& func);
Copy link

Choose a reason for hiding this comment

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

How are server side errors propagated to the client?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See below: func( nullptr, 0 ) is called.

Copy link

Choose a reason for hiding this comment

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

That's not entirely satisfactory. In a request that has an empty reply (item 3 in the list from Raphael) how can I distinguish a success from a failure?

memcpy data to block
});

_client.receive( TIMEOUT );
Copy link

Choose a reason for hiding this comment

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

To me it looks a lot more natural getting a future from _client.get and calling future.get to resolve it. Is there a way to achieve this without falling into thread-safety traps, or there's a fundamental reason for not doing it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When does the receive happen in your case? The app still needs to call it, i.e., future.wait() would deadlock in a single-threaded application. The user can still wrap it in a future with a lambda function.

Copy link

Choose a reason for hiding this comment

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

I didn't even think about it because that's an implementation detail. I'd argue that mixing request/reply (which are synchronous) with pub/sub receivers or even receivers from different servers doesn't look like a good idea. I don't know about the implementation details from zmq, but isn't the call to receive the reply blocking? Can you still poll multiple request sockets for the reply?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wrong thread, I guess. It is not an implementation detail. Client::request() is async, and Client::receive() is the sync point. In the sync point the app handles the reply from the server, which is symmetric with the subscription handling.

Copy link

Choose a reason for hiding this comment

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

I don't have a strong opinion here. I was just wondering if it could be more natural with the future as Raphael suggested. Right now it looks quite symmetric with the pub, sub, but error handling still looks clunky this way. Callback oriented APIs normally have hooks also for errors.

{
ConstURIPtr uri = livre::URI::create(data, size);
if( openAndCacheDatasource( *uri ))
return dataSource.getVolumeInfo().toBinary();
Copy link

Choose a reason for hiding this comment

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

The binary buffer returned will be moved or copied?

Copy link
Contributor Author

@eile eile Jun 7, 2017

Choose a reason for hiding this comment

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

shared, Serializable::Data has a shared_ptr + size.

routing to a function. This requires that the client and server use the same
object and version for this, i.e., the hard versioning of ZeroBuf wrt member
layout is lost. The ID is not part of the ZeroBuf, so would need separate
transport and checking in server/client.
Copy link

Choose a reason for hiding this comment

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

I see, this "answers" my first comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The first comment from my POV does not make sense. Which one do you mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

BTW: Would be happy to get KISS ideas on how to resolve this.

Copy link

Choose a reason for hiding this comment

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

My first comment is the first comment from my review, which even from your point of view is unique because it's at the very top.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess it's the one at the very bottom!?:

Using a future and a template...

See discussion there.

@@ -1,5 +1,5 @@

/* Copyright (c) 2014-2016, Human Brain Project
/* Copyright (c) 2014-2017, Human Brain Project
Copy link

Choose a reason for hiding this comment

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

Good that you renamed it, I never liked the name if this file.

@hernando
Copy link

hernando commented Jun 7, 2017

Why not call get, request? That name doesn't imply there's a return value at all.

Using a future and a template function would allow to introduce some type safety at least in the API. Even if type-safety is not implemented initially, it could be implemented later. The future also provides a natural way of propagating exceptions from the server and allows proper handling in the client. Right now it's not clear at all how errors are handled.

@eile
Copy link
Contributor Author

eile commented Jun 7, 2017

Why not call get, request? That name doesn't imply there's a return value at all.

Good idea. Will change.

@eile
Copy link
Contributor Author

eile commented Jun 7, 2017

future and a template function would allow to introduce some type safety

Some, but not all. The bigger issue is that the reply object changes memory layout and we get a client-server mismatch. Since it would still be the same class, a future would not catch (ha!) this. The reply type id needs to be part of the protocol.

@hernando
Copy link

hernando commented Jun 7, 2017

The future forces the client to tell the implementation which is the expected type, the implementation still needs some mechanism to gurantee that (which at the moment is missing). But since it also depends on changing the protocol, it's not so different from the current proposal in terms of guarantees anyway.

@eile
Copy link
Contributor Author

eile commented Jun 7, 2017

The future forces the client to tell the implementation which is the expected type, the implementation still needs some mechanism to gurantee that (which at the moment is missing). But since it also depends on changing the protocol, it's not so different from the current proposal in terms of guarantees anyway.

The implementation should not care about the type, but about the type id which is a stronger guarantee and the only one used in other parts of the API. One gains exactly nothing with the template method, because even when using the concrete type it does not prevent sending another type from the server. Note that the sync API in the first iteration used get(Serializable& req, Serializable& rep).

@eile
Copy link
Contributor Author

eile commented Jun 8, 2017

reply typing addressed in a1193a7

@eile
Copy link
Contributor Author

eile commented Jun 8, 2017

@eile
Copy link
Contributor Author

eile commented Jun 19, 2017

Ping: What's left to discuss here?

* @throw std::runtime_error if ZeroConf is not available or if session name
* is invalid
*/
ZEROEQ_API Subscriber(const URI& uri, const std::string& session);
Copy link
Contributor

Choose a reason for hiding this comment

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

Needs version bump. we should already go to the version 1.0.0 now I guess.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done, to 0.9 for now. Also squashed commits in preparation of rebase-and-merge

@eile
Copy link
Contributor Author

eile commented Jun 23, 2017

I'll eventually need an approval...

Copy link

@rdumusc rdumusc left a comment

Choose a reason for hiding this comment

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

Good for now, maybe we will reevaluate some details after having had some real world experience with the API

@eile eile merged commit 40e042a into HBPVIS:master Jun 29, 2017
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.

4 participants