Skip to content

Commit 62e8503

Browse files
Speedup Injector during concurrent node starts (#118588) (#118625)
Lets simplify this logic a little and lock on the injector instance instead of the class. Locking on the class actually wastes lots of time during test runs it turns out, especially with multi-cluster tests. Co-authored-by: Dimitris Rempapis <[email protected]>
1 parent 847dbdc commit 62e8503

File tree

8 files changed

+24
-191
lines changed

8 files changed

+24
-191
lines changed

server/src/main/java/org/elasticsearch/injection/guice/Binder.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,7 @@
6565
*
6666
* <p>The {@link Provider} you use here does not have to be a "factory"; that
6767
* is, a provider which always <i>creates</i> each instance it provides.
68-
* However, this is generally a good practice to follow. You can then use
69-
* Guice's concept of {@link Scope scopes} to guide when creation should happen
70-
* -- "letting Guice work for you".
68+
* However, this is generally a good practice to follow.
7169
*
7270
* <pre>
7371
* bind(Service.class).annotatedWith(Red.class).to(ServiceImpl.class);</pre>

server/src/main/java/org/elasticsearch/injection/guice/BindingProcessor.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,6 @@ private void putBinding(BindingImpl<?> binding) {
218218
MembersInjector.class,
219219
Module.class,
220220
Provider.class,
221-
Scope.class,
222221
TypeLiteral.class
223222
);
224223
// TODO(jessewilson): fix BuiltInModule, then add Stage

server/src/main/java/org/elasticsearch/injection/guice/InjectorBuilder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.elasticsearch.injection.guice.internal.Errors;
2121
import org.elasticsearch.injection.guice.internal.ErrorsException;
2222
import org.elasticsearch.injection.guice.internal.InternalContext;
23+
import org.elasticsearch.injection.guice.internal.Scoping;
2324
import org.elasticsearch.injection.guice.internal.Stopwatch;
2425
import org.elasticsearch.injection.guice.spi.Dependency;
2526

@@ -154,7 +155,7 @@ public static void loadEagerSingletons(InjectorImpl injector, Errors errors) {
154155
}
155156

156157
private static void loadEagerSingletons(InjectorImpl injector, final Errors errors, BindingImpl<?> binding) {
157-
if (binding.getScoping().isEagerSingleton()) {
158+
if (binding.getScoping() == Scoping.EAGER_SINGLETON) {
158159
try {
159160
injector.callInContext(new ContextualCallable<Void>() {
160161
final Dependency<?> dependency = Dependency.get(binding.getKey());

server/src/main/java/org/elasticsearch/injection/guice/Provider.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@
2828
* instances, instances you wish to safely mutate and discard, instances which are out of scope
2929
* (e.g. using a {@code @RequestScoped} object from within a {@code @SessionScoped} object), or
3030
* instances that will be initialized lazily.
31-
* <li>A custom {@link Scope} is implemented as a decorator of {@code Provider<T>}, which decides
32-
* when to delegate to the backing provider and when to provide the instance some other way.
3331
* <li>The {@link Injector} offers access to the {@code Provider<T>} it uses to fulfill requests
3432
* for a given key, via the {@link Injector#getProvider} methods.
3533
* </ul>

server/src/main/java/org/elasticsearch/injection/guice/Scope.java

Lines changed: 0 additions & 59 deletions
This file was deleted.

server/src/main/java/org/elasticsearch/injection/guice/Scopes.java

Lines changed: 15 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919
import org.elasticsearch.injection.guice.internal.InternalFactory;
2020
import org.elasticsearch.injection.guice.internal.Scoping;
2121

22-
import java.util.Locale;
23-
2422
/**
2523
* Built-in scope implementations.
2624
*
@@ -31,29 +29,27 @@ public class Scopes {
3129
private Scopes() {}
3230

3331
/**
34-
* One instance per {@link Injector}.
32+
* Scopes an internal factory.
3533
*/
36-
public static final Scope SINGLETON = new Scope() {
37-
@Override
38-
public <T> Provider<T> scope(final Provider<T> creator) {
39-
return new Provider<T>() {
34+
static <T> InternalFactory<? extends T> scope(InjectorImpl injector, InternalFactory<? extends T> creator, Scoping scoping) {
35+
return switch (scoping) {
36+
case UNSCOPED -> creator;
37+
case EAGER_SINGLETON -> new InternalFactoryToProviderAdapter<>(Initializables.of(new Provider<>() {
4038

4139
private volatile T instance;
4240

43-
// DCL on a volatile is safe as of Java 5, which we obviously require.
4441
@Override
45-
@SuppressWarnings("DoubleCheckedLocking")
4642
public T get() {
4743
if (instance == null) {
4844
/*
49-
* Use a pretty coarse lock. We don't want to run into deadlocks
50-
* when two threads try to load circularly-dependent objects.
51-
* Maybe one of these days we will identify independent graphs of
52-
* objects and offer to load them in parallel.
53-
*/
54-
synchronized (InjectorImpl.class) {
45+
* Use a pretty coarse lock. We don't want to run into deadlocks
46+
* when two threads try to load circularly-dependent objects.
47+
* Maybe one of these days we will identify independent graphs of
48+
* objects and offer to load them in parallel.
49+
*/
50+
synchronized (injector) {
5551
if (instance == null) {
56-
instance = creator.get();
52+
instance = new ProviderToInternalFactoryAdapter<>(injector, creator).get();
5753
}
5854
}
5955
}
@@ -62,54 +58,10 @@ public T get() {
6258

6359
@Override
6460
public String toString() {
65-
return String.format(Locale.ROOT, "%s[%s]", creator, SINGLETON);
61+
return creator + "[SINGLETON]";
6662
}
67-
};
68-
}
69-
70-
@Override
71-
public String toString() {
72-
return "Scopes.SINGLETON";
73-
}
74-
};
75-
76-
/**
77-
* No scope; the same as not applying any scope at all. Each time the
78-
* Injector obtains an instance of an object with "no scope", it injects this
79-
* instance then immediately forgets it. When the next request for the same
80-
* binding arrives it will need to obtain the instance over again.
81-
* <p>
82-
* This exists only in case a class has been annotated with a scope
83-
* annotation and you need to override this to "no scope" in your binding.
84-
*
85-
* @since 2.0
86-
*/
87-
public static final Scope NO_SCOPE = new Scope() {
88-
@Override
89-
public <T> Provider<T> scope(Provider<T> unscoped) {
90-
return unscoped;
91-
}
92-
93-
@Override
94-
public String toString() {
95-
return "Scopes.NO_SCOPE";
96-
}
97-
};
98-
99-
/**
100-
* Scopes an internal factory.
101-
*/
102-
static <T> InternalFactory<? extends T> scope(InjectorImpl injector, InternalFactory<? extends T> creator, Scoping scoping) {
103-
104-
if (scoping.isNoScope()) {
105-
return creator;
106-
}
107-
108-
Scope scope = scoping.getScopeInstance();
109-
110-
// TODO: use diamond operator once JI-9019884 is fixed
111-
Provider<T> scoped = scope.scope(new ProviderToInternalFactoryAdapter<T>(injector, creator));
112-
return new InternalFactoryToProviderAdapter<>(Initializables.of(scoped));
63+
}));
64+
};
11365
}
11466

11567
}

server/src/main/java/org/elasticsearch/injection/guice/internal/AbstractBindingBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ protected void checkNotScoped() {
7777
return;
7878
}
7979

80-
if (binding.getScoping().isExplicitlyScoped()) {
80+
if (binding.getScoping() != Scoping.UNSCOPED) {
8181
binder.addError(SCOPE_ALREADY_SET);
8282
}
8383
}

server/src/main/java/org/elasticsearch/injection/guice/internal/Scoping.java

Lines changed: 5 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -16,78 +16,22 @@
1616

1717
package org.elasticsearch.injection.guice.internal;
1818

19-
import org.elasticsearch.injection.guice.Scope;
20-
import org.elasticsearch.injection.guice.Scopes;
19+
import org.elasticsearch.injection.guice.Injector;
2120

2221
/**
2322
* References a scope, either directly (as a scope instance), or indirectly (as a scope annotation).
2423
* The scope's eager or laziness is also exposed.
2524
*
2625
* @author [email protected] (Jesse Wilson)
2726
*/
28-
public abstract class Scoping {
29-
27+
public enum Scoping {
3028
/**
3129
* No scoping annotation has been applied. Note that this is different from {@code
3230
* in(Scopes.NO_SCOPE)}, where the 'NO_SCOPE' has been explicitly applied.
3331
*/
34-
public static final Scoping UNSCOPED = new Scoping() {
35-
36-
@Override
37-
public Scope getScopeInstance() {
38-
return Scopes.NO_SCOPE;
39-
}
40-
41-
@Override
42-
public String toString() {
43-
return Scopes.NO_SCOPE.toString();
44-
}
45-
46-
};
47-
48-
public static final Scoping EAGER_SINGLETON = new Scoping() {
49-
50-
@Override
51-
public Scope getScopeInstance() {
52-
return Scopes.SINGLETON;
53-
}
54-
55-
@Override
56-
public String toString() {
57-
return "eager singleton";
58-
}
59-
60-
};
61-
32+
UNSCOPED,
6233
/**
63-
* Returns true if this scope was explicitly applied. If no scope was explicitly applied then the
64-
* scoping annotation will be used.
34+
* One instance per {@link Injector}.
6535
*/
66-
public boolean isExplicitlyScoped() {
67-
return this != UNSCOPED;
68-
}
69-
70-
/**
71-
* Returns true if this is the default scope. In this case a new instance will be provided for
72-
* each injection.
73-
*/
74-
public boolean isNoScope() {
75-
return getScopeInstance() == Scopes.NO_SCOPE;
76-
}
77-
78-
/**
79-
* Returns true if this scope is a singleton that should be loaded eagerly in {@code stage}.
80-
*/
81-
public boolean isEagerSingleton() {
82-
return this == EAGER_SINGLETON;
83-
}
84-
85-
/**
86-
* Returns the scope instance, or {@code null} if that isn't known for this instance.
87-
*/
88-
public Scope getScopeInstance() {
89-
return null;
90-
}
91-
92-
private Scoping() {}
36+
EAGER_SINGLETON
9337
}

0 commit comments

Comments
 (0)