-
-
Notifications
You must be signed in to change notification settings - Fork 812
FilteringParserDelegate
can go into an infinite loop if underlying parser is non-blocking
#1144
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
Can you provide reproducible test cases? Volunteers appreciate time savers like that. |
Sure! Here is a testcase, probably needs some refinement (especially around asserting timeout with assertj). public void testFilteringNonBlockingParser() throws IOException {
JsonFactory factory = new JsonFactoryBuilder().build();
JsonParser nonBlockingParser = factory.createNonBlockingByteArrayParser();
ByteArrayFeeder inputFeeder = (ByteArrayFeeder) nonBlockingParser.getNonBlockingInputFeeder();
String json = "{\"first\":1,\"second\":2}";
byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8);
JsonParser filteringParser = new FilteringParserDelegate(nonBlockingParser, new JsonPointerBasedFilter("/second"),
TokenFilter.Inclusion.ONLY_INCLUDE_ALL, false);
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.schedule(() -> {
try {
inputFeeder.feedInput(jsonBytes, 0, jsonBytes.length);
} catch (IOException e) {
throw new RuntimeException(e);
}
}, 500, TimeUnit.MILLISECONDS);
Future<JsonToken> future = Executors.newSingleThreadExecutor().submit(() -> filteringParser.nextToken());
Assertions.assertThat(future)
.succeedsWithin(Duration.ofSeconds(5))
.isNotNull()
.isNotEqualTo(JsonToken.NOT_AVAILABLE);
} |
Quick note: yes, first 2 seem like easy enough oversights to fix (alas, I don't know of a way to properly prevent the issue of missing overrides). But I suspect you may be right in that handling Let's start with #1146 tho, add overrides where they belong ( |
Ok, so... my first instinct is that handling of The reason I think handling is requires all over the place is that basically But... I am not convinced it would be impossible to do this. :) @simonbasle If you have time to try to add changes, I'd be happy to review code. One thing that would be very important, I think, would be to add test cases that feed input (byte-by-byte, in bigger chunks); support for this exists, see tests in |
hey @cowtowncoder, thanks for the analysis. unfortunately I don't think I will have the possibility to try to add changes anytime soon 😞 hopefully there is some kind of low hanging fruit where the parsing doesn't work but fails fast ? (e.g. throwing an exception if the underlying parser is returning |
@simonbasle All legit, and also bit challenging to do... esp. since patch release typically should not change behavior minus bug fixes. And while there are tests, I think due to complexity of filtering delegation, there are still gaps. For patch release, ideally I do think I'd like to improve things here, and maybe I can. But it does need to fight wrt all other priorities. But I'll keep this on my ever-expanding todo-list. |
Quick notes:
|
Not sure what you mean by that. That said I can understand the apprehensiveness of failing the constructor and how to document that. If you feel the risk is too big in terms of impact on existing users, I guess a big WARN logging would perhaps be the next best thing 🤷♂️
👍 |
Ok, let me start by explaining one possibly valid use case for Async parser: if the whole content is indeed read into buffer, it seems possible to use filtering parser without issue (in this case Looking at tests, there is:
which actually verifies that usage works (assuming content is indeed all available). As to logging WARNING: Jackson does zero-logging, as matter of principle (well, some modules like Afterburner do log one line for specific failure) so that is not available as a technique. But I am not sure it'd be good way even if we could do that. But I think there might be another way: create method for validating given parser -- overridable -- which will by default fail on async parser. I think I;ll go with that. |
Forgot to say: thank you @simonbasle for both reporting this and going through with some of the options. Beyond initial blocking of usage, it'd be nice to add handling of |
Ok. So, as per This does lead to the obvious problem: should we block supported usage (as per test) to prevent rather nasty issues for "bad" usage? I guess I should add test from here to at least reproduce failure case. |
Adding test which does reproduce problem, but cannot quite pinpoint where. Need to see how to debug where the infinite loop occurs (I realize there are probably a few places... but easier to find one etc) |
I'm trying to make my head around what need to be achieved here. After debugging for a bit, the first infinite loop that I caught occurs because Second point: I feel like the test itself is problematic, Lines 63 to 70 in 0b21629
If we're feeding in a separate thread after a delay, how do we expect the assertion to succeed with no input (The assertion will run before the input is fed)? (Unless the goal of the filter delegate to keep looking until input is fed, and token is found?) Looking forward to your insights, as I feel like I'm missing something critical here. |
Re-reading what I said earlier, I think that my thinking was that:
As to test, executor; I'm pretty sure that was just meant to bail out from test when infinite loop occurs. Does this make more sense? |
Hey, thanks @cowtowncoder the direction and goal is more clear now. |
Jackson version (BOM):
2.15.2
While experimenting with
JsonPointer
in Spring Framework, I tried to build up on the existingFilteringParserDelegate
and associatedJsonPointerBasedTokenFilter
. Unfortunately, this doesn't seem to work with a non-blockingJsonParser
delegate, for several reasons:canParseAsync()
(ends up returning the default interface implem =>false
)getNonBlockingInputFeeder()
(same as above =>null
)JsonToken.NOT_AVAILABLE
While the first two are easily circumvented by extending
FilteringParserDelegate
, the last one is the truly problematic one. It results in going down the code path of a "scalar value", and will continuously calldelegate.nextToken()
which continues to result inNOT_AVAILABLE
.This leads to an infinite loop when attempting to filter based on a
JsonPointer
on top of a non-blocking parser.Is there anything fundamentally preventing
FilteringParserDelegate
from being compatible with non-blocking parsers that I might have overlooked? Otherwise I think it's pretty close, hopefully that can be fixed 😄The text was updated successfully, but these errors were encountered: