> 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 > >