-
Notifications
You must be signed in to change notification settings - Fork 7.6k
AsyncOnSubscribe strange behavior #3341
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
Comments
@akarnokd Thanks for writing this up. I'll try to pick through it for problems. Regarding the first item ( |
Why should be rollback? The AsyncOnSubscribe is marked as |
I'm afraid the problem with this component will either delay the release of 1.0.15 again and may degrade the reputation of the project due to a "knowingly buggy release" otherwise.
I don't understand the intent of the operator and why the request amounts are re-purposed the way they are. Did you try to implement something like an eager-concatMap? |
Sure we can roll back if that's what you think is best. I'll continue to hash out the changes in my branch and merge once you have had a chance to fully review the code.
No. Although that was necessary for this feature this was not the only goal. As I explained in #3003, we need an api that gives users an easy way to create observables that respect back pressure semantics. The |
I almost never found a case where the request amount are hand-crafted. The only place this happens is when an operators such as observeOn() and merge() batch-out an arbitrary child request into standard element counts of 128 (by default). It seems you want to turn a traditionally externally requested API (i.e., Observable getData(long maxAmount) into something that uses the producer as a channel to communicate in a different semantics with the source. I think this is what full-duplex observables of the future may look like (some call them channels). This may be the aim for 3.x but is out of scope right now. I'd take a step back and look at why you want to do such give-me-N type of use. Perhaps the right combination of defer, concat, subjects can get your desired behavior through a transformer instead. |
We have that use case here at Netflix and this is a frequently requested feature. @benjchristensen and I believe that we need ways to create observables that respect back pressure (without the jagged edges of |
I went back and read #3003 and realized the similarities with #3020. I was interested in achieving the same aim except as an In short I agree with @akarnokd that a combination of existing operators can provide this functionality. I used a combination of Here is the backpressure respecting implementation of @stealthcode My apologies I haven't reviewed the PR in any detail but have been relying on the diligence of @akarnokd and waiting for what is distilled between you. I think the intent of the PR is terrific, that easier backpressure implementation techniques for custom observables and operators is really valuable. It's definitely something I come across frequently. |
I'm looking for a replacement for @davidmoten could you give me a code example of something that generates data for each request from an asynchronous source using composition of existing operators? At first glance the |
I've been reading #3003 and am starting to understand the async use case a bit more. My comments above were more relevant to Correct me if I'm wrong but the suggested technique is to on every request of n to generate an observable to fulfill those n items and concatenate the emissions of that observable with the observable generated from the previous request. I'm wondering if we can explore the use case a bit further rather than discuss the implementation? Just to make sure we've covered alternatives. The use case that I've seen that speaks to me the strongest in this context is pagination. Without anything new I would be looking at doing something like: int pageSize=1000;
int limit = 10000;
Observable
.range(0,limit)
.concatMap(i -> restClient.getRange(i*1000, (i+1)*1000)
.subscribeOn(scheduler)); The catch with that is that we want To get a good non-blocking asynchronous solution the first thing I'd look for is an overload on In the meantime I wonder why this PR isn't simplified to contributing the |
ah, still getting up to speed. Noticed that @stealthcode and @akarnokd have already discussed the eager concatMap. I'd like to see that operator as part of the public API of this PR. |
@davidmoten I was under the same impression as you that the requirement is most likely a form of pagination + eager subscription with maintaining order. What I understood is that whenever a consumer calls This behavior, contrary to the name of the proposed operator, works only in synchronous mode because the moment you apply I sense that this operator originates from a requirement were implementation details leaked into the requirement itself. |
I'm also a bit uncomfortable about relying on sensible request sizes to be matched to observable creations. It does seem to open up a need for a request batching operator as mentioned by @akarnokd. I'd prefer to see a PR with an eager concat operator (which is where the majority of the tricky work is seemingly) followed by a separate PR with the Observable creation utility that would use the eager concat (without relying on requests being matched to observable creations as presented by this PR currently). |
There is nothing preventing multiple requests from a subscriber while the first request is being processed. This could happen when each response is asynchronous or if observeOn or merge are changed to request more as the buffers are being drained.
Can you elaborate on this? Firstly it's not an operator. Secondly, the requirement is that we give users a way to create observables that support back pressure even in cases where the data is being fulfilled asynchronously. I fail to see how the "implementation details leaked". The purpose of the AsyncOnSubscribe is to expose the requests in a simple interface so applications can respond with asynchronous abstractions and an observable makes sense as a consistent wrapper of asynchronous work. @davidmoten Operators do not expose a simple to understand request-response protocol. The AsyncOnSubscribe allows a service to respond to a request with an asynchronous response. It'd be great to have the eager concat operator but that's not the goal of #3003 and I do not think that the two should be associated or necessarily dependent. For each request, respond asynchronously with data emissions fulfilling that request. This is a means to fill downstream buffers progressively without excess buffering, dropping, etc. If we expected users to I need a way for application writers to construct ordered data in response to one or more Do you have objections to the goal or design? |
See my proposed fixes in #3356. |
I was under the impression that the functionality of eager concat map would be the core of I'm still concerned about the practicality of using requests to create Observables. Suppose I have an observable built using AsyncOnSubscribe and I'm hoping to request(1000) at a time from it for pagination purposes. Let's get a subscriber to control the requests: source.subscribe(paginatingSubscriber); Now as code evolves I would imagine wanting to insert an operator in this chain. @akarnokd has already mentioned the effect source
.filter(condition)
.subscribe(paginatingSubscriber); Suppose the To handle this you might expect that an operator could be stuck just after source
.bufferRequests(1000, 100, TimeUnit.MILLISECONDS)
.filter(condition)
.subscriber(subscriber); A design question then is should AsyncOnSubscribe incorporate the functionality of this made-up operator |
This simply sounds like an inefficient implementation of It's an interesting idea to try to code an AsyncOnSubscribe that buffers it's requests using a subject and operators internal to it's implementation. Here is a gist of what I'm thinking. This exercise might inform some modifications to the api to make that easier. For instance, an implementation that used a subject as the state and then onNexted the requested value then accumulated/throttled the requests.
I'd rather see functionality like |
As an addendum to my last post, the time windowed source
.bufferRequests(1000)
.filter(condition)
.subscriber(subscriber);
I'll be interested to see your suggestions on this one. I'll read the rest of your answer later (time to go home!). |
I've been asked to show test cases that prove the existence of bugs I mentioned in #3203.
First, I tried to show the race between inner termination and outer unsubscribtion, but the following simple code returns an error instead of any value:
It seems that
AsyncOuterSubscriber
always requestsLong.MAX_VALUE
even if the child requests nothing. This value ends up as -1 and hence the error.I've debugged
testOnUnsubscribeHasCorrectState
and it seems there is an initial request ofMAX_VALUE
which triggers state 1. the value is also put into the queue which is polled on the next iteration yielding anotherMAX_VALUE
and state 2. At this point, the main reachesrequestMore(2)
which triggers state 3 andrequestMore(3)
does nothing since the inner has calledonComplete()
.The following code hangs after a few dozen iterations. It tries to overlap the termination of the inner range with a concurrent cancellation.
I couldn't get further with the other bugs because the behavior of this two tests prevent any other progress.
Since I don't understand what the original intent was nor how the request-juggling supposed to work, I can't offer any suggestion on what to and how to fix the operator. Therefore, I suggest rolling back the merge and extending the unit test with more checks, including real concurrent ones (not just TestScheduler-based).
The text was updated successfully, but these errors were encountered: