-
-
Notifications
You must be signed in to change notification settings - Fork 15.9k
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
Pluggable algorithm to choose next EventLoop #2470
Conversation
@jakobbuchgraber after reading your comments I think all of this makes a lot of sense... Now looking at the code directly... |
success = true; | ||
} catch (Exception e) { | ||
// TODO: Think about if this is a good exception type | ||
throw new IllegalStateException("failed to create a child event loop", e); | ||
} finally { | ||
if (!success) { | ||
for (int j = 0; j < i; j ++) { | ||
children[j].shutdownGracefully(); | ||
for (EventExecutor e : this.chooser.children()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We basically used the (int j = ....) stuff to make sure the JVM does not create an iterator for this kind of work and so produce more GC
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@normanmaurer How come? My impression is that this code is hardly ever executed at all. Only in cases when Netty crashes or is shutting down?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true... I only wanted to highlight why we had it like this :)
/cc @spodila who is exploring similar logic for RxJava event loop Schedulers. |
@spodila welcome :) |
Thanks. :) |
@@ -27,14 +35,12 @@ | |||
* Abstract base class for {@link EventExecutorGroup} implementations that handles their tasks with multiple threads at | |||
* the same time. | |||
*/ | |||
public abstract class MultithreadEventExecutorGroup extends AbstractEventExecutorGroup { | |||
public abstract class MultithreadEventExecutorGroup<T extends EventExecutor> extends AbstractEventExecutorGroup { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@normanmaurer What do you think of this change? I introduced the generic parameter, because EventExecutorChooser#children()
returns a List<T>
and generic collections are invariant. Otherwise the EventExecutor*
naming, that is used internally would have been exposed to the user (https://github.com/netty/netty/pull/2470/files#diff-642dc25c87844818aa9bd66f58448c32R21).
I think if I returned an array instead, we would not need generics. Might actually be the smart thing to do, if I think about it now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem with an array is that the user may modify it ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The EventLoopChooser
object is only accessible from inside the
Multithread*Group
. It's not exposed via any "public" API. So that
should not be a problem?
On Wed, May 7, 2014 at 8:18 PM, Norman Maurer notifications@github.comwrote:
In
common/src/main/java/io/netty/util/concurrent/MultithreadEventExecutorGroup.java:@@ -27,14 +35,12 @@
- Abstract base class for {@link EventExecutorGroup} implementations that handles their tasks with multiple threads at
- the same time.
*/
-public abstract class MultithreadEventExecutorGroup extends AbstractEventExecutorGroup {
+public abstract class MultithreadEventExecutorGroup extends AbstractEventExecutorGroup {The problem with an array is that the user may modify it ?
—
Reply to this email directly or view it on GitHubhttps://github.com//pull/2470/files#r12392937
.
Mit freundlichen Grüßen / Best Regards
Jakob Buchgraber
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true... so yeah +1 :)
I just pushed an updated commit. Here's what's new:
Furthermore, I did some testing on the accuracy of Netty's Task Scheduler and was pleasantly surprised that even under high load its timing was generally very accurate (to +/- 1/100 seconds). However, I do realize that its accuracy depends mostly on how long it takes for I also did some load testing to see if frequent calls to Basically I did something like this: public class DefaultMetricsCollector implements MetricsCollector {
private long last;
public void readBytes(long bytes) {
if (last != 0) {
long now = System.nanoTime();
// update every second
if (now - last > 1000000000) {
bytesRead.tick();
last = System.nanoTime();
}
} else {
last = System.nanoTime();
}
bytesRead.update(bytes);
}
} I didn't notice any difference in performance, however I didn't use a profiler and my Netty application didn't do much computation either besides sending back some bytes. I haven't done much work on my GSoC project this week as I was busy with university stuff and @normanmaurer is on vacation anyway :) |
@jakobbuchgraber so what is the status of this ... ? |
Motivation: Currently when a new event loop is needed to register a channel our EventLoopGroup implementations just use a round-robin like algorithm to choose the next event loop. Unfortunately this is not always good enough as different event loops may become more busy than others over time. This is especially true when an application handles different kinds of connections (long-living and short-living). This change allows developers to plug in their own algorithms to choose the next event loop and to collect metrics about the busyness of the event loops. See netty#1230 Modifications: - TODO Result: - Developers can plug in their own algorithms into MultithreadEventLoopGroup constructors. - Developers can gather metrics/feedback from the event loops and use the data in their custom algorithms.
Hello,
here is a first draft of what I think how #1230 should be implemented.
EpollEventLoopGroup
andNioEventLoopGroup
get additional constructors to accept anEventLoopChooser
object. At its heart theEventLoopChooser
has a methodEventLoop next(SocketAddress remoteAddress)
, which is called on each accepted channel and maps each channel to anEventLoop
. TheremoteAddress
parameter is the IP address of the accepted channel. That's useful in cases where a developer wants to map channels to event loops based on IP address or subnet. However, it might also be null, in cases where e.g. a task is assigned an event loop. So developers have to ensure that their code works in both cases. Most developers will simply want to ignore the parameter and assign channels to event loops based on some other criteria, e.g. provided by aMetricsProvider
.So what's a
MetricsProvider
? AMetricsProvider
is mostly (85,7% to be precise) aMetricsCollector
and the latter one has lots of methods that are called in the differentChannel
implementations to collect data about their inside. Like the number of registered channels, transferred bytes, etc. Each event loop gets its ownMetricsCollector
, that way a developer can implement aMetricsCollector
to provide metrics on a per event loop basis and use this information in theEventLoopChooser#next(SocketAddress remoteAddress)
method to decide which channel is mapped to which event loop.In order to make metrics computation easier, some basic data structures for (what I believe to be) common use cases are provided. One such data structure is what I call the
TickValueHistory
.A
TickValueHistory
object simply provides the sum of the last n ticks (think n seconds) through a call toTickValueHistory#value()
. It can be used to answer queries like "how many bytes were transferred in the last n seconds?". It's usage is illustrated in theDefaultMetricsProvider
class. Basically, a timer increments an integercurrentTick
every second and whenever bytes are read from a channelTickValueHistory#update(currentTick, bytesRead)
is called. The main advantage of this design is that it avoids lots of calls toSystem.nanoTime()
and reduces the "hot path" to two simple integer additions. Furthermore, I am aware that on the accuracy of Netty's scheduled task times can't be counted on (1 second, might really be 2 seconds if the event loop is real busy). I am not sure how much of a problem this will be in practice and so I plan to do some experiments to see it for myself. If times turn out to be really inaccurate, an easy fix would be to simply call System.nanoTime() every tick (think "netty second") and normalize the sum to a real second (that is, divide the sum by the actual time passed).Also I did not use Fenwick Trees (as opposed to what I wrote in my proposal). The reason being that I think that the generality they provide is simply not needed. Basically, with a Fenwick Tree one could get the sum for arbitrary periods of time (at a greater cost), while the current implementation only allows to look back a specific period of time. In other words, with Fenwick trees the
numPastTicks
parameter would not be in the constructor but in thevalue
method.What's still missing besides tests and comments?
EventLoopChooser
implementations to demonstrate the usefulness of all of this.