google / guava

Google core libraries for Java
Apache License 2.0
50.23k stars 10.91k forks source link

AbstractFuture.afterCommit? #2886

Open cpovirk opened 7 years ago

cpovirk commented 7 years ago

In a recent review, I said:

I wonder if there's a place for some kind of afterCommit() hook that, like afterDone(), runs upon a call to set()/setException()/cancel() but then also runs on a call to setFuture()....

The idea is that it could reduce duplication between afterDone and other "We just computed the value, so we're done with the inputs" code, like here:

https://github.com/google/guava/blob/2d4c517de9f12ba4dd2d9f720e700d7c835ecc4c/guava/src/com/google/common/util/concurrent/TrustedListenableFutureTask.java#L83

However, I'm not sure that a one-size-fits-all solution will work well here. Compare what we do in AbstractTransformFuture:

https://github.com/google/guava/blob/2d4c517de9f12ba4dd2d9f720e700d7c835ecc4c/guava/src/com/google/common/util/concurrent/AbstractTransformFuture.java#L89

There, we null out the input-future field even before we run the function. (Hmm, I guess that this prevents toString from giving full detail.... We might want to think about that more.)

I suppose that TrustedListenableFutureTask could similarly null out this.task immediately after reading it to a local variable. If so, then we could extract that nulling-out into a method that we call from both run and afterDone. But it's already just a single assignment. Even in the case of AbstractTransformFuture, it's only 2 assignments. Plus, it's nice to keep those assignments in the same method as the reads just to avoid needing to think more about cross-method dependencies. It would be one thing if we could automatically null out the fields at the beginning of run, but we can't because, well, we need the fields in run :) There is probably some possible crazy solution here -- a series of RunnableTrustedFutureWithInputs1, RunnableTrustedFutureWithInputs2, etc. classes with 1, 2, etc. fields of user-configurable type, all automatically read and passes as parameters to a run(T1, T2, ...) method (and pendingToString(T1, T2, ...)? and maybe even have a special class for "one of the inputs is a Future, so you should cancel it automatically?") -- but I don't think we need more layers of superclasses right now unless we have a very good reason. (Edit: And the T1, T2 parameters won't have useful names by default, though obviously subclasses can name them whatever they want.)

...OK, maybe I'm a little intrigued, in part because AbstractTransformFuture/AbstractCatchingFuture may share more code with TrustedListenableFutureTask/InterruptibleTask if we ever make them interruptible. At that point, we could consider sharing more code. But I don't think we're there yet.

cpovirk commented 6 years ago

One tiny unfortunate thing is that, if we have both afterCommit and afterDone, we need to think about which one happens first in simple cases like set. I think it pretty clearly has to be afterCommit first, since that's what happens with complicated setFuture cases. So my tiny concern is more that we have to think about the ordering between two separate methods at all. But maybe that's no worse than what we already deal with when the "afterCommit" logic is split across multiple methods, often duplicated. And hopefully our tests for the setFuture case would catch any bugs.

cpovirk commented 6 years ago

Oh, and I just noted in a code review that it would be just slightly nice for AbstractTransformFuture to null out function before calling maybePropagateCancellationTo(inputFuture). We would get that for free if we nulled out function in afterCommit.

cpovirk commented 6 years ago

(When completing a future, we'd have to know whether afterCommit has been called. But this is just asking whether the previous state was SetFuture. That information is available to all callers of complete(), so they could pass it as a parameter.)

cpovirk commented 5 years ago

The lack of this was a contributor to internal bug 139750022.

cpovirk commented 5 years ago

I prototyped this internally in CL 266457646. Some thoughts:

cpovirk commented 5 years ago

(Another somewhat related idea I had was to give AbstractFuture a concept of "set to the result provided by the given InterruptibleTask." This would complicate AbstractFuture further (its implementation and API both, really). But it would also help with the cleanup and interruption of the task, and it might make #1989 simpler when we hopefully do that someday.)