Reusing JsonGenerator and FilteringGeneratorDelegate

20 views
Skip to first unread message

Volkan Yazıcı

unread,
Oct 21, 2018, 5:02:00 PM10/21/18
to jackson-user
Hi,

I have a JsonGenerator and FilteringGeneratorDelegate usage as follows:

ByteArrayOutputStream outputStream = threadLocalOutputStreamRef.get();
try (JsonGenerator jsonGenerator = jsonFactory.createGenerator(outputStream)) {
try (JsonGenerator jsonGeneratorDelegate = new FilteringGeneratorDelegate(jsonGenerator, tokenFilter, true, true)) {
// ...
}
}

Is it possible to reuse JsonGenerator and FilteringGeneratorDelegate instances in a way attached to the local thread context?

Best.

Tatu Saloranta

unread,
Oct 22, 2018, 12:59:02 AM10/22/18
to jackson-user
No, JsonGenerator is designed for use-once life-cycle and does not
support reuse.
Instances are light-weight so there shouldn't usually be much benefit
from attempting to reuse instances; same goes with
FilteringGeneratorDelegate.

-+ Tatu +-

Volkan Yazıcı

unread,
Oct 24, 2018, 8:35:48 AM10/24/18
to jackson-user
I am at the edge of making log4j2-logstash-layout gc-free. And at
this stage my only (GC) obstacle is the aforementioned two classes.
I need to instantiate them at each encode(LogEvent) call and this
spoils the entire gc-free magic.

What I can do is create a ThreadLocal parade of ByteBufferOutputStream->JsonGenerator->FilteringGeneratorDelegate.
This allows me to implement encode() as follows:
  1. get ThreadLocal resources
  2. reset BBOS
  3. writeStartObject()
  4. encode fields
  5. writeEndObject()
  6. flip and drain the BBOS to the Log4j BB sink
Though this approach has an unfortunate showstopper: If the 4th step
leaves the JsonGenerator and/or the FilteringGeneratorDelegate in a
dirty state -- consider exceeding BBOS capacity, etc. -- all the
subsequent calls will fail.

I will deeply appreciate any kind of help on this issue.

Best.

Volkan Yazıcı

unread,
Oct 24, 2018, 9:35:47 AM10/24/18
to jackson-user
Further, I do have a nasty hack as follows, which I really really don't want to use:

private static void rescueJsonGeneratorState(ByteBuffer byteBuffer, JsonGenerator jsonGenerator) {
List<CheckedRunnable> rescueAction = Arrays.asList(
() -> jsonGenerator.writeNumber(0), // Try completing a missing field value.
jsonGenerator::writeEndArray, // Try closing an array.
jsonGenerator::writeEndObject); // Try closing an object.
for (int rescueActionId = 0;; rescueActionId = ++rescueActionId % rescueAction.size()) {
byteBuffer.clear();
try {
jsonGenerator.writeStartObject();
jsonGenerator.writeEndObject();
jsonGenerator.flush();
return;
} catch (IOException error) {
if (error instanceof JsonGenerationException) {
try {
rescueAction.get(rescueActionId).run();
} catch (Throwable ignored) {
// Ignored.
}
} else {
throw new RuntimeException(error);
}
}
}
}

Tatu Saloranta

unread,
Oct 25, 2018, 9:57:38 PM10/25/18
to jackso...@googlegroups.com
On Wed, Oct 24, 2018 at 5:35 AM Volkan Yazıcı <volkan...@gmail.com> wrote:
>
> I am at the edge of making log4j2-logstash-layout gc-free. And at
> this stage my only (GC) obstacle is the aforementioned two classes.
> I need to instantiate them at each encode(LogEvent) call and this
> spoils the entire gc-free magic.
>
> What I can do is create a ThreadLocal parade of ByteBufferOutputStream->JsonGenerator->FilteringGeneratorDelegate.
> This allows me to implement encode() as follows:
>
> get ThreadLocal resources
> reset BBOS
> writeStartObject()
> encode fields
> writeEndObject()
> flip and drain the BBOS to the Log4j BB sink
>
> Though this approach has an unfortunate showstopper: If the 4th step
> leaves the JsonGenerator and/or the FilteringGeneratorDelegate in a
> dirty state -- consider exceeding BBOS capacity, etc. -- all the
> subsequent calls will fail.
>
> I will deeply appreciate any kind of help on this issue.

Ah ok. Thank you for providing more information. This sounds like an
interesting and useful challenge. :)
I like to ask these things ("is that really necessary") first as I
often learn something new.

One thing that may be worth mentioning in case you weren't aware: you
can just leave `JsonGenerator` open:
it does not enforce that content consists of just a single JSON Value.
In fact this is how many stream data generation
(and parsing, via JsonParser) systems work.
There is separate "root value separator" that is used between root
values: it defaults to " " (single space), but may be
reconfigured to, for example, linefeed.

Whether this will work with FilteringGeneratorDelegate is bit
different question.

But do you think that reuse by not closing would work from processing
perspective.

-+ Tatu +-




>
> Best.
>
> On Monday, October 22, 2018 at 6:59:02 AM UTC+2, Tatu Saloranta wrote:
>>
>> On Sun, Oct 21, 2018 at 2:02 PM Volkan Yazıcı <volkan...@gmail.com> wrote:
>> >
>> > Hi,
>> >
>> > I have a JsonGenerator and FilteringGeneratorDelegate usage as follows:
>> >
>> > ByteArrayOutputStream outputStream = threadLocalOutputStreamRef.get();
>> > try (JsonGenerator jsonGenerator = jsonFactory.createGenerator(outputStream)) {
>> > try (JsonGenerator jsonGeneratorDelegate = new FilteringGeneratorDelegate(jsonGenerator, tokenFilter, true, true)) {
>> > // ...
>> > }
>> > }
>> >
>> > Is it possible to reuse JsonGenerator and FilteringGeneratorDelegate instances in a way attached to the local thread context?
>>
>> No, JsonGenerator is designed for use-once life-cycle and does not
>> support reuse.
>> Instances are light-weight so there shouldn't usually be much benefit
>> from attempting to reuse instances; same goes with
>> FilteringGeneratorDelegate.
>>
>> -+ Tatu +-
>
> --
> You received this message because you are subscribed to the Google Groups "jackson-user" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to jackson-user...@googlegroups.com.
> To post to this group, send email to jackso...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

Volkan Yazıcı

unread,
Oct 26, 2018, 10:03:57 AM10/26/18
to jackson-user
I can see that JsonGenerator keeps the door open for further input, that
indeed allows me to reuse it. (Thanks for the "root value separator" config,
did not know about it.) But that is also the reason I need to reach for
rescueJsonGeneratorState(). It would be really nice if I could have somehow
"reset" the state of JsonGenerator (which is actually Utf8JsonGenerator in
this particular case) and FilteringGeneratorDelegate. Then before each
serialization attempt, I can just reset my BB+generators and am ready to go.

Tatu Saloranta

unread,
Nov 5, 2018, 4:54:23 PM11/5/18
to jackso...@googlegroups.com
On Fri, Oct 26, 2018 at 7:03 AM Volkan Yazıcı <volkan...@gmail.com> wrote:
>
> I can see that JsonGenerator keeps the door open for further input, that
> indeed allows me to reuse it. (Thanks for the "root value separator" config,
> did not know about it.) But that is also the reason I need to reach for
> rescueJsonGeneratorState(). It would be really nice if I could have somehow
> "reset" the state of JsonGenerator (which is actually Utf8JsonGenerator in
> this particular case) and FilteringGeneratorDelegate. Then before each
> serialization attempt, I can just reset my BB+generators and am ready to go.

It would probably be easier to allow resetting of delegate type, but
less so for regular generators.

But I am still bit unclear on specific need: what part are you
specifically hoping to reset?
If it'd be output state (which Object/Array open etc), only close()
would change that.
But if you are thinking of underlying destination (OutputStream,
Writer), buffering, those are not designed to be changed, and I
wouldn't want to change that.

Then again, if resetting of delegate (by changing target) was enough,
that might be easy enough.

-+ Tatu +-

Volkan Yazıcı

unread,
Nov 10, 2018, 4:24:13 PM11/10/18
to jackson-user
Hrm... I am interested in resetting the output state and
apparently that is not trivial to implement at this stage.
Let me see how far will my rescueJsonGeneratorState()
let me go. In the worst case, I will create a GitHub
issue for the feature.

BTW, in Utf8JsoGenerator#close(), I see a trick similar
to my rescueJsonGeneratorState():

/* 05-Dec-2008, tatu: To add [JACKSON-27], need to close open
* scopes.
*/
// First: let's see that we still have buffers...
if ((_outputBuffer != null)
&& isEnabled(Feature.AUTO_CLOSE_JSON_CONTENT)) {
while (true) {
JsonStreamContext ctxt = getOutputContext();
if (ctxt.inArray()) {
writeEndArray();
} else if (ctxt.inObject()) {
writeEndObject();
} else {
break;
}
}
}

Thanks so much for the help Tatu.
Really appreciated.
Keep up the good job!
Reply all
Reply to author
Forward
0 new messages