-
Notifications
You must be signed in to change notification settings - Fork 183
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
loadbalancer: HostSelector can be rebuilt each time the DefaultLoadBalancer gets a host set update #2774
loadbalancer: HostSelector can be rebuilt each time the DefaultLoadBalancer gets a host set update #2774
Conversation
Motivation: For algorithms like weighted round robin and weighted random we need to build some data structures for each unique host set. Modifications: Add a `.rebuild(List<Host>)` method to the `HostSelector` interface. This lets a host potentially rebuild data structures it needs to properly balance but only when there is a host set update.
546ed33
to
ebb41e4
Compare
import static org.junit.jupiter.api.Assertions.assertThrows; | ||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
||
abstract class LoadBalancerTestScaffold { |
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.
This is all pulled from LoadBalancerTest
.
@@ -127,33 +111,12 @@ | |||
import static org.mockito.Mockito.times; | |||
import static org.mockito.Mockito.verify; | |||
|
|||
abstract class LoadBalancerTest { | |||
abstract class LoadBalancerTest extends LoadBalancerTestScaffold { |
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.
All the deletions here were simply moved to LoadBalancerTestScaffold
.
d951ba1
to
cc412c6
Compare
|
||
SequentialExecutor(final ExceptionHandler exceptionHandler) { | ||
this.exceptionHandler = requireNonNull(exceptionHandler, "exceptionHandler"); | ||
} | ||
|
||
public boolean isCurrentThreadDraining() { |
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.
This is only really necessary so we can make DefaultLoadBalancer.usedHosts
non-volatile and still have a thread safe DefaultLoadBalancer.usedAddresses()
method, but it feels 'nice'. Feedback welcome on it's utility.
Also see the comment in usedAddresses()
.
// If we're already in the executor we can't submit a task and wait for it without deadlock but | ||
// the access is thread safe anyway so just go for it. | ||
if (sequentialExecutor.isCurrentThreadDraining()) { | ||
return sequentialUsedAddresses(); | ||
} | ||
CompletableFuture<List<Entry<ResolvedAddress, List<C>>>> future = new CompletableFuture<>(); | ||
sequentialExecutor.execute(() -> future.complete(sequentialUsedAddresses())); | ||
try { | ||
// This method is just for testing, so it's fine to do some awaiting. | ||
return future.get(5, TimeUnit.SECONDS); | ||
} catch (Exception ex) { | ||
throw new AssertionError("Failed to get results", ex); | ||
} |
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.
If we wanted to, this pattern could be lifted into a method of the form T SequentialExecutor.submit(Callable<T>)
.
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.
LGTM, please get a 2nd eye on SequentialExecutor
and removal of volatile
from DefaultLoadBalancer
servicetalk-loadbalancer/src/main/java/io/servicetalk/loadbalancer/DefaultLoadBalancer.java
Outdated
Show resolved
Hide resolved
servicetalk-loadbalancer/src/main/java/io/servicetalk/loadbalancer/DefaultLoadBalancer.java
Outdated
Show resolved
Hide resolved
servicetalk-loadbalancer/src/main/java/io/servicetalk/loadbalancer/DefaultLoadBalancer.java
Outdated
Show resolved
Hide resolved
servicetalk-loadbalancer/src/main/java/io/servicetalk/loadbalancer/DefaultLoadBalancer.java
Outdated
Show resolved
Hide resolved
servicetalk-loadbalancer/src/main/java/io/servicetalk/loadbalancer/LoadBalancingPolicy.java
Outdated
Show resolved
Hide resolved
@@ -502,6 +509,24 @@ public Completable closeAsyncGracefully() { | |||
|
|||
@Override | |||
public List<Entry<ResolvedAddress, List<C>>> usedAddresses() { |
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.
I may be missing something here, but isn't that a check-then-act issue? What guarantees that the condition still holds after the IF.
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.
Can you clarify what your concern is? If it's whether the result from usedAddresses()
is still valid, then yes: it's always going to be racy. Since it's only used for tests to inspect the internal state the usage is rather constrained it should be no worse than it always was. If you means the if
block on line 514 then we know what the current sequence of calls are going to be for this thread and reason about it for the lifetime of this method call at least.
servicetalk-loadbalancer/src/main/java/io/servicetalk/loadbalancer/HostSelector.java
Outdated
Show resolved
Hide resolved
* pattern to use is to make the internal state effectively immutable and rebuilds (see below) generate new | ||
* immutable instances as necessary. The interface is expected to adhere to the following rules: | ||
* | ||
* <li>The {@link HostSelector#selectConnection(Predicate, ContextMap, boolean)} method will be used |
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.
So to clarify: the old host selector could still be called even if a new rebuilt host selector has been returned?
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.
That is not strictly specified. It's possible to envision a use case where the old-selector gets 'locked down' when a new selector is built. However, the common case (and perhaps the only real case) should be that sending a request to an old selector will be treated as if it had simply raced ahead of the rebuild.
Motivation:
For algorithms like weighted round robin and random we need to
build some data structures for each unique host set.
Modifications:
.rebuild(List<Host>)
method to theHostSelector
interface. This lets a host potentially rebuild data structures it
needs to properly balance but only when there is a host set update.
.isHealthy()
method toHostSelector
. This cleaned upsome questions about which host list is used and may also be
helpful for future composition of selectors.