Ambiguity for promise::fulfill_anonymous(0)

Issue #109 resolved
Dan Bonachea created an issue

Consider this program:

#include <upcxx/upcxx.hpp>
#include <iostream>

int main(int argc, char *argv[]) {
    upcxx::init();

    upcxx::promise<> pro1;
    upcxx::future<> f = pro1.get_future();
    f.then([]() { std::cout << "Hello from " << upcxx::rank_me() << std::endl; } );
    pro1.fulfill_anonymous(0);  // no effect
    pro1.fulfill_anonymous(1);  // triggers future callback
    pro1.fulfill_anonymous(0);  // semantics unclear

    upcxx::finalize();
    return 0;
}

The promise::fulfill_anonymous(count) calls above all satisfy the currently specified Precondition (the only Precondition for calls on empty promises):

The dependency count of this promise is greater than or equal to count.

In particular, the call promise::fulfill_anonymous(0) always trivially satisfies this Precondition, by virtue of the invariant that promise dependency counters are always non-negative. However, the semantics of this call are unclear for the special case of the last such call shown above, where the promise dependency count has previously reached zero (emphasis added):

Subtracts count from the dependency counter. If this produces a zero counter value, the associated future is set to ready, and callbacks that are waiting on the future are executed on the calling thread before this function returns.

It's unclear from this wording what happens for the special case of promise::fulfill_anonymous(0) where the dependency count is already zero. Ie, does the operation dependency_counter -= 0 "produce a zero counter value"? One possible interpretation of the current wording is this special-case call "triggers" the promise a second time (eg producing a second duplicate output line in the example above) -- probably not what we want.

I see two alternatives:

  1. Clarify the semantics wording for promise::fulfill_anonymous to unambiguously specify that the promise triggers at most once during its lifetime, when the dependency counter transitions from non-zero to zero.
  2. Strengthen the promise::fulfill_anonymous Precondition to require the dependency count is greater than zero, effectively prohibiting calls to promise::fulfill_anonymous(0) after the promise is triggered.

FWIW: The example above currently crashes with an assertion failure in the runtime for the call in question:

UPC++ assertion failure on rank 0 [.../upcxx.debug.gasnet1_seq.smp/include/upcxx/future/core.hpp:474]

[0] #23 0x00000000100021d8 in upcxx::assert_failed (file=0x101e0048 <std::ignore+6> "/home/bonachea/UPC/inst-upcxx/upcxx.debug.gasnet1_seq.smp/include/upcxx/future/core.hpp", line=474, msg=0x0) at /home/bonachea/UPC/upcxx/src/diagnostic.cpp:64
[0] #24 0x00000000101d82cf in upcxx::detail::future_header_result<>::readify() (this=0x6000576c0) at /home/bonachea/UPC/inst-upcxx/upcxx.debug.gasnet1_seq.smp/include/upcxx/future/core.hpp:474
[0] #25 0x00000000101d9d18 in upcxx::promise<>::fulfill_anonymous(long) (this=0xffffcbd0, n=0) at /home/bonachea/UPC/inst-upcxx/upcxx.debug.gasnet1_seq.smp/include/upcxx/future/promise.hpp:63
[0] #26 0x00000000100011da in main (argc=1, argv=0xffffcc60) at fulfill-zero.cpp:12

which seems to favor the second resolution.

Comments (4)

  1. Amir Kamil

    I am strongly in favor of making the precondition for both require_anonymous and fulfill_anonymous that count is greater than zero. This is something I suggested in the past, but there was pushback, and I don't remember the details. To me, it doesn't make semantic sense to allow negative values, and I don't see a benefit to allowing 0 as well.

  2. Dan Bonachea reporter

    Currently require_anonymous and fulfill_anonymous can both move the dependency counter in either direction, the only differences are (1) the "sense" of the signed count argument (add versus subtract), and (2) the former is forbidden from "triggering" the promise.

    I agree with Amir that the generality of non-positive count arguments is probably rarely useful. However I can imagine contrived examples where the counter adjustment is computed and could go in either direction, which would require the insertion of a branch in user code to call the right function if we strengthened the preconditions to require positive count.

    Amir's suggestion would also solve this problem, but I'm still arguing for one of the two "lighter-weight" fixes to the semantic ambiguity in my original report.

  3. Dan Bonachea reporter

    In the 1/10 meeting we resolved Amir will fix the spec by March

    • never trigger a promise multiple times
    • allow zero count argument
    • disallow negative count argument
    • prohibit all calls after satisfy (precond internal counter > 0)
  4. Log in to comment