> On Nov. 8, 2013, 12:39 a.m., Benjamin Hindman wrote:
> > 3rdparty/libprocess/include/process/dispatch.hpp, lines 92-93
> > <https://reviews.apache.org/r/15319/diff/1/?file=380494#file380494line92>
> >
> >     Why can't we capture this in Promise::associate instead? I think we 
> > want these semantics even for code as simple as this:
> >     
> >     Future<T> future;
> >     Promise<T> promise;
> >     promise.associate(future);
> >     promise.future().discard();
> >     CHECK(future.isDiscarded());
> >     
> >     
> >     What about changing Promise::associate thusly:
> >     
> >     template <typename T>
> >     bool Promise<T>::associate(const Future<T>& future)
> >     {
> >       if (!f.isPending()) {
> >         return false;
> >       }
> >     
> >       future
> >         .onReady(std::tr1::bind(&Future<T>::set, f, 
> > std::tr1::placeholders::_1))
> >         .onFailed(std::tr1::bind(&Future<T>::fail, f, 
> > std::tr1::placeholders::_1))
> >         .onDiscarded(std::tr1::bind(&Future<T>::discard, f));
> >     
> >       f.onDiscarded(std::tr1::bind(&Future<T>::discard, future)); // You 
> > might need a copy of future to get this to compile since discard is not a 
> > const function.
> >     
> >       return true;
> >     }
> >     
> >     Also, maybe we should add a TODO that makes it such that you can't both 
> > associate a promise and set a promise ... but we can do that in a later 
> > commit.
> 
> Jie Yu wrote:
>     Ben, I thought about this today. I need to clarify the semantics here:
>     
>     Case 1:
>     Future<T> future;
>     Promise<T> promise;
>     promise.future().discard();
>     promise.associate(future);
>     
>     
>     Case 2:
>     Future<T> future;
>     Promise<T> promise;
>     promise.associate(future);
>     promise.future().discard();
>     
>     In case2, obviously, 'future' should be discarded. However, in case1, do 
> you think 'future' should be discarded or not? One may argue that since 
> 'promise.future()' is discarded before the association, we should not discard 
> 'future' (just like you did in your code). However, if we don't, we may run 
> into issues in chaining dispatch future discarding.
>     
>     For example:
>     class Foo : public Process<Foo> {
>       Future<bool> func() { return future; }
>       Future<bool> future;
>     };
>     
>     Future<bool> future = dispatch(pid, &Foo::func);
>     future.discard();
>     
>     We would expect at this moment, the 'future' field in class Foo should 
> have been discarded. However, it's likely that the association has not 
> happened yet (i.e., pdispatcher has not been called yet), thus 'future' field 
> in class Foo may not be discarded if I follow the semantics in your code.
>     
>     Any thoughts on this?
>     
>     Also, can you elaborate why 'you can't both associate a promise and set a 
> promise'? Thanks!
> 
> Jie Yu wrote:
>     The following code works (tested and make check passes), but has a 
> different semantics as yours.
>     
>     template <typename T>
>     bool Promise<T>::associate(const Future<T>& future)
>     {
>       f.onDiscarded(std::tr1::bind(&Future<T>::discard, future));
>     
>       if (!f.isPending()) {
>         return false;
>       }
>     
>       future
>         .onReady(std::tr1::bind(&Future<T>::set, f, 
> std::tr1::placeholders::_1))
>         .onFailed(std::tr1::bind(&Future<T>::fail, f, 
> std::tr1::placeholders::_1))
>         .onDiscarded(std::tr1::bind(&Future<T>::discard, f));
>     
>       return true;
>     }
> 
> Benjamin Hindman wrote:
>     Hey Jie!
>     
>     When I said "you can't both associate a promise and set a promise' I was 
> referring to the fact that a future should only "transition/be satisfied" 
> once. When you 'associate' a promise with a future you're effectively 
> "transitioning/satisfying" the promise's future "in the future" (i.e., 
> whenever the associated future transitions/is satisfied).
>     
>     Given the semantics of Promise::associate (as they stand in the code base 
> today) there is the potential for some weirdness. Consider:
>     
>     Promise<T> promise1;
>     Promise<T> promise2;
>     promise1.associate(promise2.future());
>     promise1.set(...);
>     promise2.set(...); // This line has no impact on 'promise1' because it is 
> already set in the previous line.
>     
>     Basically, after a promise has been associated we should not let someone 
> set the promise as well! I like to think of 'promise1' as in another state 
> (i.e., ASSOCIATED: still PENDING but not "settable").
>     
>     Cancellation is trickier. In general, cancellation goes "both ways" 
> (i.e., you don't need a Promise to discard, you can just use 
> Future::discard). Thus, I think it's reasonable to move 'f.onDiscarded(...)' 
> to the top of Promise::associate.
>     
>     Of course, it's slightly clumsy to remember that cancellation is "both 
> ways" and that something like the following does not work:
>     
>     Future<T> future;
>     Promise<T> promise;
>     promise.set(...);
>     promise.associate(future); // Does nothing!
>     
>     Alternatively, what about making associate create a true alias? I.e., 
> something like:
>     
>     
>     template <typename T>
>     bool Promise<T>::associate(const Future<T>& future)
>     {
>       if (!f.isPending() && !future.isPending()) {
>         return false;
>       }
>     
>       f
>         .onReady(std::tr1::bind(&Future<T>::set, future, 
> std::tr1::placeholders::_1))
>         .onFailed(std::tr1::bind(&Future<T>::fail, future, 
> std::tr1::placeholders::_1))
>         .onDiscarded(std::tr1::bind(&Future<T>::discard, future));
>     
>       future
>         .onReady(std::tr1::bind(&Future<T>::set, f, 
> std::tr1::placeholders::_1))
>         .onFailed(std::tr1::bind(&Future<T>::fail, f, 
> std::tr1::placeholders::_1))
>         .onDiscarded(std::tr1::bind(&Future<T>::discard, f));
>     
>       return true;
>     }
>     
>     
>     Of course, there could be a race here, e.g., two threads/processes 
> associating promises with one another's futures. I believe this is not a 
> problem assuming that you always associate before you set. Note that I'm 
> deliberately ignoring the case where there are two threads/processes 
> simultaneously calling associate on the _same_ promise, but we could protect 
> against that too using the future's lock. Thoughts?

Ben, I thought about the true alias implementation today, and I think it is 
very complicated and I cannot find an elegant solution to avoid the race 
conditions here.

I checked our code base again today. Seems that in our case, what we usually 
want is a one way association. A common pattern is like the following:

// 1 - Give a future to client first. Pass the promise to some other callback. 
The actual future is not yet available.
Promise<T> promise;
return promise.future();

// 2 - Associate the actual future with the passed promise so that the client 
can be notified when the actual future is ready.
Future<T> future = func(...);
promise.associate(future);

(e.g. dispatch, then, registrar)

We usually don't set/fail the promise directly in these cases. In other words, 
we don't need a two way alias in these cases. In fact, the client usually 
cannot set/fail the promise directly as the promise is usually not visible to 
client. A notable exception is discard because Future can be directly 
discarded. Therefore, I think it make sense to make an exception for the 
discard case here (just like the exception we made by allowing a Future to be 
directly discarded without involving a Promise).

Because of that, I would like to add a TODO here and look into them later. For 
now, I will do the following. Let me know your thoughts!

template <typename T>
bool Promise<T>::associate(const Future<T>& future)
{
  // TODO(jieyu): Making 'f' a true alias of 'future'.
  f.onDiscarded(std::tr1::bind(&Future<T>::discard, future));

  if (!f.isPending()) {
    return false;
  }

  future
    .onReady(std::tr1::bind(&Future<T>::set, f, std::tr1::placeholders::_1))
    .onFailed(std::tr1::bind(&Future<T>::fail, f, std::tr1::placeholders::_1))
    .onDiscarded(std::tr1::bind(&Future<T>::discard, f));

  return true;
}


- Jie


-----------------------------------------------------------
This is an automatically generated e-mail. To reply, visit:
https://reviews.apache.org/r/15319/#review28471
-----------------------------------------------------------


On Nov. 7, 2013, 7:41 p.m., Jie Yu wrote:
> 
> -----------------------------------------------------------
> This is an automatically generated e-mail. To reply, visit:
> https://reviews.apache.org/r/15319/
> -----------------------------------------------------------
> 
> (Updated Nov. 7, 2013, 7:41 p.m.)
> 
> 
> Review request for mesos, Benjamin Hindman, Ben Mahler, and Vinod Kone.
> 
> 
> Repository: mesos-git
> 
> 
> Description
> -------
> 
> Say you have a process defined as follows:
> 
> class FooProcess : public Process<FooProcess>
> {
> public:
>   Future<bool> func() { return future; }
> private:
>   Future<bool> future;
> };
> 
> Then you call dispatch that returns a future:
> 
> Future<bool> f = dispatch(process, &FooProcess::func);
> 
> If the user discards the future 'f', we expect the 'future' field in 
> FooProcess also being discarded. However, this is not the case currently.
> 
> This patch fixed this issue.
> 
> 
> Diffs
> -----
> 
>   3rdparty/libprocess/include/process/dispatch.hpp b337a87 
>   3rdparty/libprocess/src/tests/process_tests.cpp 7848599 
> 
> Diff: https://reviews.apache.org/r/15319/diff/
> 
> 
> Testing
> -------
> 
> make check
> 
> Also:
> 3rdparty/libprocess/tests --gtest_repeat=1000
> 
> 
> Thanks,
> 
> Jie Yu
> 
>

Reply via email to