
Hi Braddock, Your suggestions on using futures to for return values and "out" parameters are dead on with what I was hoping for! Also, I like the prospect of using futures to distinguish between sync and async calls, rather than providing dual functionality directly in the library... Details below:
I personally would suggest looking at how futures can make the async/sync elements easier.
Imagine I have a function 'rpc' which calls the passed function and arguments remotely and returns a future<T>, where T is the return type.
Along the lines of: future<T> rpc(F function, INARG1, ... , INARGN, OUTARG1, ... OUTARGN);
1) Synchronous behavior: cout << "1+2 = " << rpc(add, 1, 2) << "\n";
Great! Now the rpc framework offers only an asynchronous interface, and the futures are what can make it synchronous... I don't see any drawbacks to this at the moment, and it makes the rpc code a great deal simpler. Also, looks like the same thing would work if the actual function had, say, void (int &) signature, with the int & being an "out" parameter, and I wanted to distinguish between sync and async behavior by passing an int & versus a future<int> & - I think I will need to accept such arguments in the rpc using a wrapper anyway, so if the user passes reference to the data type (rather than a reference to the future) then the reference just gets assigned the value of an internal future (making the behavior synchronous).
2) Async behavior: future<int> fi = rpc(add, 1, 2); ...doing other things... cout << "1+2 = " << fi.get() << "\n"; //will block if fi is not yet complete
Got that to work.
3) rpc call fails: future<int> fi = rpc(add, 1, 2); try { cout << "1+2 = " << fi << "\n"; //will throw if fi contains exception } catch (rpc_connect_error &e) { ... }
4) I want to cancel the rpc if it isn't ready yet: future<int> fi = rpc(add, 1, 2); ...doing other things... fi.cancel(); // cancels if not yet complete
Cancellation by cancelling the feature is a cool idea... I assume the promise can be notified that all futures have been cancelled via a callback? Speaking of... if a function call has many outstanding promises/futures (e.g., a return value and a few "out" arguments), is there a way to group those all together so that they can be cancelled as a group?
5) I want a basic timeout fi.timed_wait(100 /*ms*/); if (!fi.ready()) fi.cancel();
OK, but I think similar functionality should also be added to the rpc interface - it might be useful to be able to set a flat timeout for an individual function (or all functions), and then cancel the promise in case of a timeout. What might be useful in this respect, is the ability to tell a future (at the time it is assigned to a promise) to auto-cancel itself in X time (or, perhaps, to tell the promise the same thing).
6) My remote call may return an exception (and my rpc library has some exception transportation mechanism) try { cout << "1+2 = " << fi << "\n"; } catch (...) { cout << "ERROR\n"; }
I do plan to add exception transportation. Is it possible to have the future throw a custom (specified by the promise) exception? Your point 3) suggests that this is possible also.
7) I have both a return value and an "Out" argument: future<int> multiplied; future<int> fi = rpc(add_and_mult, 1, 2, multiplied); cout << "1+2 = " << fi << "\n"; cout << "1*2 = " << multiplied << "\n";
ATM, seems like future doesn't have a default constructor, so future<int> multiplied; doesn't compile. Also, if the argument was "InOut" rather than just "Out", we'll need some mechanism (maybe already exists) that will pass the "in" value with the future, and then the future can be reassigned to the rpc promise by rpc. It's either this or forcing the user to pass a pair for the argument (the current value as well as the future).
This is the ideal application of futures. My futures library can support all of the above uses today without modification. As a bonus, then any code which uses futures can transparently use the rpc library as a back-end (except for dispatch of course)...a future is a future is a future. I'll work with you as I prepare for boost submission if you want to explore this route.
Using futures to get the return value I was able to get working pretty easily - works beautifully, no problems at all. Using them to get modified values of "out" parameters I can't get working without a default constructor (unless I stick the futures inside the call class). To try the rest of the things I need to get the rpc side of the functionality up to speed. But yeah, I definitelly want to explore this route! If I catch enough time, I'll wrap up the next iteration of the library with as much future-related functionality as I can. What would help me the most is some documentation of the features - I've been getting a lot from the code and the test cases, but sometimes the intended use escapes me. Thanks, Stjepan