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

Add ability to configure the queue capacity for ChunkedOutput #5621

Conversation

rutterpaul-personal
Copy link
Contributor

@rutterpaul-personal rutterpaul-personal commented Apr 23, 2024

Adding constructors which set the queue capacity.

This allows for calling ChunkedOutput.write from multiple concurrent threads until the queue is full. Subsequent write calls will block until the queue is emptied by flushQueue. This change allows to prevent memory issues in case of slow clients. Basically, a backpressure mechanism based on the queue size.

This change provides a way to call write on ChunkedOutput from multiple threads to improve throughput, while preventing memory issues in case of slow clients. The queue is bounded and thus cannot grow endlessly.

The code now uses put (https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/LinkedBlockingQueue.html#put-E-) instead of add (https://docs.oracle.com/javase/8/docs/api/java/util/AbstractQueue.html#add-E-) so it blocks when the queue is full.

Adding constructors which sets the queue capacity.

This allows for calling write from different threads until the queue is full. Subsequent write calls will block until the queue is emptied.
This change allows to prevent memory issues in case of slow clients. Basically, a backpressure mechanism based on the queue size.

This change provides a way to call write on ChunkedOutput from multiple threads to improve throughput, while preventing memory issues in case of slow clients. The queue is bounded and thus cannot grow endlessly.
@jansupol
Copy link
Contributor

@rutterpaul-personal @paulrutter This PR just adds 6 new constructors. Would it be better to have a builder instead?

@paulrutter
Copy link

paulrutter commented Apr 23, 2024

@jansupol
Yes, the number of constructors is not ideal. How would you see a builder pattern for this?
Keeping the current constructors as is and instead add a builder to ChunkedOutput which takes all configuration options?

  • queueCapacity
  • chunkType
  • chunkDelimiter
  • asyncContextProvider

That could work for me.
What do you think of adding a configuration for the queue size though?

We recently ran into memory issues because we no longer synchronized on calling the ChunkedOutput.write method.
This change would allow to still do so, but with backpressure in place due to the queue.put blocking until it's drained.

@jansupol
Copy link
Contributor

Yes, I'd keep the constructors as they are, for the backward compatibility, without adding any new (except for the one accepting the builder, setting up all the values set on the builder to the ChunkedOutput).

@paulrutter
Copy link

Ok, i will work on that instead.

@paulrutter
Copy link

paulrutter commented Apr 23, 2024

Something like this then?

final ChunkedOutput<String> output = ChunkedOutput.<String>newBuilder(String.class).queueCapacity(10).chunkDelimiter("\r\n".getBytes()).build();

I cannot get the chunkType into the builder properly, as i cannot call super conditionally. Any ideas on that?
I now defaulted to String.class when it's null, which seems a safe default. I'm not even sure if we still need to set the chunkType, as the example above makes sure that String is used.

@jansupol what do you think about the chunkType? Can i drop it from the builder?

I noticed that the chunkType is now always required in the public constructors, so i made it a required argument to the newBuilder method. That solves the issue.

@paulrutter
Copy link

If these changes are ok, documentation probably needs updating on https://eclipse-ee4j.github.io/jersey.github.io/documentation/2.42/async.html#chunked-output and it would need a unit test to test the builder.

Add e2e test that uses builder with queue capacity of 2.
Update documentation with an example of the builder with the capacity
Copyright
Make chunkType a required argument to the builder.
Change builder methods.
Update doc.
@jansupol
Copy link
Contributor

The builder looks good. I'd prefer the name builder over newBuilder, though.

The chunkType is required to know the type in runtime, or the type can be known from the ChunkedOutput subclass (hence the protected constructors), the same way the GenericType works.

So, with the protected constructor functionality, If needed, I think the builder could work with constructs like:

new ChunkedOutput<List<String>>(ChunkedOutput.builder(size).build()) { }

protected ChunkedOutput(Builder builder) {
  super(builder.getChunkedType() != null ? builder.getChunkedType() : getType());
  ...
}

wdyt?

@paulrutter
Copy link

@jansupol That would make sense, except that i cannot call getType in the constructor before super has been called

Cannot reference 'GenericType.getType()' before supertype constructor has been called

Or do i misunderstand?

@jansupol
Copy link
Contributor

@paulrutter My bad, it's not that simple. Would need to have two constructors for that... And two builders... Maybe something like

class TypedBuilder extends Builder {
   private Type chunkedType; // move here from Builder
   TypedBuilder(Type type){...}
   ChunkedOutput build() {return new ChunkedOutput(this);} // perhaps throwing a warning in Builder
}

static TypedBuilder builder(Type type) { return new TypedBuilder(type());}
static Builder builder(){return new Builder());

protected ChunkedOutput(Builder) {  super(); .... }
private ChunkedOutput(TypedBuilder builder) { super(builder.getType()); ... }

Perhaps too complicated at the moment ... unless you would have a use case for it.

Add two builders, one with and one without type.
Update docs
@paulrutter
Copy link

Any idea in which version this will land?

@jansupol
Copy link
Contributor

2.43 the next week hopefully

@paulrutter
Copy link

I assume it will also land in 3.x?

@senivam senivam merged commit be13798 into eclipse-ee4j:2.x Apr 25, 2024
7 checks passed
@senivam senivam added this to the 2.43 milestone Apr 25, 2024
@jansupol
Copy link
Contributor

I assume it will also land in 3.x?

Sure, 3.0 right after 2.43, 3.1 a bit later, 4.0 next.

@paulrutter
Copy link

Confirmed it works on 2.43.

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 this pull request may close these issues.

None yet

4 participants