Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RequestFuture#get blocks forever if an in-flight request is cancelled #85

Open
jpd236 opened this issue Sep 7, 2017 · 2 comments · May be fixed by #103
Open

RequestFuture#get blocks forever if an in-flight request is cancelled #85

jpd236 opened this issue Sep 7, 2017 · 2 comments · May be fixed by #103
Milestone

Comments

@jpd236
Copy link
Collaborator

jpd236 commented Sep 7, 2017

RequestFuture#get waits for either onResponse or onErrorResponse to be called before attempting to return a result. However, if a request is cancelled, by design, neither callback will be executed. This means get() will block forever (or until the user-specified timeout, if one is given).

Proposal to fix:

  • Add an optional CancelListener that can be set on a request
  • Have cancel() only set mCanceled = true when called from the main thread, and queue a runnable to do so otherwise
  • Call the cancel listener when setting mCanceled = true (on the main thread)
  • Have RequestFuture implement CancelListener and set itself as the listener in setRequest, and call notify() in the listener.
  • Ensure we use setRequest everywhere we use RequestFuture so it is cancellable
@jpd236 jpd236 added this to the 1.0.1 milestone Sep 21, 2017
jpd236 added a commit to jpd236/volley that referenced this issue Sep 27, 2017
- Provide stronger thread safety guarantees by locking access to any
variable which can be mutated during the processing of a request (i.e.
by the CacheDispatcher/NetworkDispatcher threads) and dispatching
response callbacks while holding the same lock that is held when
setting mCanceled. Unfortunately there's not much we can do about
alternative ResponseDelivery mechanisms, which are hopefully rare.

- Add a cancel listener interface and use it to prevent RequestFuture
from blocking for the complete timeout (or indefinitely) if a request
is canceled.

- Since listeners are often Android component classes like Activitys,
clear them upon cancellation. Unfortunately, this must be done in each
Request subclass to work, assuming the subclass follows the standard
pattern of holding the response listener as a member. But this enables
concerned apps to resolve this leak.

Fixes google#15
Fixes google#85
jpd236 added a commit to jpd236/volley that referenced this issue Sep 27, 2017
- Provide stronger thread safety guarantees by locking access to any
variable which can be mutated during the processing of a request (i.e.
by the CacheDispatcher/NetworkDispatcher threads) and dispatching
response callbacks while holding the same lock that is held when
setting mCanceled. Unfortunately there's not much we can do about
alternative ResponseDelivery mechanisms, which are hopefully rare.

- Add a cancel listener interface and use it to prevent RequestFuture
from blocking for the complete timeout (or indefinitely) if a request
is canceled.

- Since listeners are often Android component classes like Activitys,
clear them upon cancellation. Unfortunately, this must be done in each
Request subclass to work, assuming the subclass follows the standard
pattern of holding the response listener as a member. But this enables
concerned apps to resolve this leak.

Fixes google#15
Fixes google#85
@jpd236 jpd236 linked a pull request Sep 27, 2017 that will close this issue
@jpd236 jpd236 modified the milestones: 1.0.1, 1.1.0 Oct 3, 2017
@jpd236
Copy link
Collaborator Author

jpd236 commented Oct 3, 2017

Unfortunately I think the work to add a proper CancelListener and have it adhere to a reasonable contract is a bit larger than I'd like to include in 1.0.1.

#103 is my initial attempt at this, which had some comments that would need to be addressed. In particular, if we're going to expose this API externally, we should provide some reasonable guarantees about the thread we invoke the listener on, ideally in a consistent way with what we do for response / error response callbacks. On the other hand, if we implement obfuscation as in #102, we could potentially keep this API private to Volley for now and only use it to prevent RequestFuture from blocking forever

@jpd236
Copy link
Collaborator Author

jpd236 commented Jun 19, 2018

I took one more look to see if we could do this in any way that wouldn't require any API changes.

Short of a proper CancelListener interface, we need some way of calling notifyAll() on the RequestFuture after the request has been canceled. This would then have to go through Request#cancel. However, checking whether mErrorListener is an instance of RequestFuture there would introduce a dependency from the base volley package to the toolbox package, which historically we have not done. (I question whether this matters, but wouldn't change this for a point fix). And calling notifyAll() unconditionally on any ErrorListener could lead to unexpected behavior for listeners other than ErrorListener who are relying on wait/notify for their own internal semantics. (In a well-behaved app, it shouldn't, because spurious wakeups are always a possibility, but again, we don't want to be introducing risk like that in a point-fix release). We can't create an interface for RequestFuture to implement because this would be a new public API (so that RequestFuture could depend on it from a different package).

We could fix RequestFuture#cancel, but this doesn't really do much to address the problem IMO which is going through RequestQueue's cancel interface while something else is waiting on the future.

So - leaving this on the 1.2.0 milestone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant