From f98d75f932c436823b1b1c04c9233d8d3aa94f2f Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Mon, 11 Jun 2018 17:13:25 +0300 Subject: [PATCH 1/5] Added Lazy --- .../org/elasticsearch/common/util/Lazy.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 server/src/main/java/org/elasticsearch/common/util/Lazy.java diff --git a/server/src/main/java/org/elasticsearch/common/util/Lazy.java b/server/src/main/java/org/elasticsearch/common/util/Lazy.java new file mode 100644 index 0000000000000..221d364aae8a1 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/common/util/Lazy.java @@ -0,0 +1,61 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.util; + +import org.elasticsearch.common.CheckedSupplier; + +import java.util.Objects; +import java.util.function.Consumer; + +public class Lazy { + + private final CheckedSupplier supplier; + private final Consumer finalizer; + private volatile T value; + + public Lazy(CheckedSupplier supplier) { + this(supplier, v -> {}); + } + + public Lazy(CheckedSupplier supplier, Consumer finalizer) { + this.supplier = supplier; + this.finalizer = finalizer; + } + + public T getOrCompute() throws E { + final T result = value; // Read volatile just once... + return result == null ? maybeCompute(supplier) : result; + } + + public synchronized void clear() { + if (value != null) { + finalizer.accept(value); + value = null; + } + } + + private synchronized T maybeCompute(CheckedSupplier supplier) throws E { + if (value == null) { + value = Objects.requireNonNull(supplier.get()); + } + return value; + } + +} From c54ffa1d440cba8becddde6eb03d1e2360d79f47 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Mon, 11 Jun 2018 17:42:17 +0300 Subject: [PATCH 2/5] GCS removed double map (config + clients) --- .../gcs/GoogleCloudStorageService.java | 59 +++++++++---------- 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/plugins/repository-gcs/src/main/java/org/elasticsearch/repositories/gcs/GoogleCloudStorageService.java b/plugins/repository-gcs/src/main/java/org/elasticsearch/repositories/gcs/GoogleCloudStorageService.java index 9fe78dfb9970b..621b611dec1a1 100644 --- a/plugins/repository-gcs/src/main/java/org/elasticsearch/repositories/gcs/GoogleCloudStorageService.java +++ b/plugins/repository-gcs/src/main/java/org/elasticsearch/repositories/gcs/GoogleCloudStorageService.java @@ -34,21 +34,25 @@ import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.common.util.Lazy; + import java.io.IOException; import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; import static java.util.Collections.emptyMap; public class GoogleCloudStorageService extends AbstractComponent { - /** Clients settings identified by client name. */ - private volatile Map clientsSettings = emptyMap(); - /** Cache of client instances. Client instances are built once for each setting change. */ - private volatile Map clientsCache = emptyMap(); + /** + * Dictionary of client instances. Client instances are built lazily from the + * latest settings. + */ + private AtomicReference>> clientsCache = new AtomicReference<>(emptyMap()); public GoogleCloudStorageService(final Settings settings) { super(settings); @@ -57,18 +61,20 @@ public GoogleCloudStorageService(final Settings settings) { /** * Refreshes the client settings and clears the client cache. Subsequent calls to * {@code GoogleCloudStorageService#client} will return new clients constructed - * using these passed settings. + * using the parameter settings. * * @param clientsSettings the new settings used for building clients for subsequent requests - * @return previous settings which have been substituted */ - public synchronized Map - refreshAndClearCache(Map clientsSettings) { - final Map prevSettings = this.clientsSettings; - this.clientsSettings = MapBuilder.newMapBuilder(clientsSettings).immutableMap(); - this.clientsCache = emptyMap(); - // clients are built lazily by {@link client(String)} - return prevSettings; + public synchronized void refreshAndClearCache(Map clientsSettings) { + // build the new lazy clients + final MapBuilder> newClientsCache = MapBuilder.newMapBuilder(); + for (final Map.Entry entry : clientsSettings.entrySet()) { + newClientsCache.put(entry.getKey(), new Lazy(() -> createClient(entry.getKey(), entry.getValue()))); + } + // make the new clients available + final Map> oldClientCache = this.clientsCache.getAndSet(newClientsCache.immutableMap()); + // release old clients + oldClientCache.values().forEach(Lazy::clear); } /** @@ -83,37 +89,26 @@ public GoogleCloudStorageService(final Settings settings) { * (blobs) */ public Storage client(final String clientName) throws IOException { - Storage storage = clientsCache.get(clientName); - if (storage != null) { - return storage; - } - synchronized (this) { - storage = clientsCache.get(clientName); - if (storage != null) { - return storage; - } - storage = SocketAccess.doPrivilegedIOException(() -> createClient(clientName)); - clientsCache = MapBuilder.newMapBuilder(clientsCache).put(clientName, storage).immutableMap(); - return storage; + final Lazy lazyClient = clientsCache.get().get(clientName); + if (lazyClient == null) { + throw new IllegalArgumentException("Unknown client name [" + clientName + "]. Existing client configs: " + + Strings.collectionToDelimitedString(clientsCache.get().keySet(), ",")); } + return lazyClient.getOrCompute(); } /** * Creates a client that can be used to manage Google Cloud Storage objects. The client is thread-safe. * * @param clientName name of client settings to use, including secure settings + * @param clientSettings name of client settings to use, including secure settings * @return a new client storage instance that can be used to manage objects * (blobs) */ - private Storage createClient(final String clientName) throws Exception { - final GoogleCloudStorageClientSettings clientSettings = clientsSettings.get(clientName); - if (clientSettings == null) { - throw new IllegalArgumentException("Unknown client name [" + clientName + "]. Existing client configs: " - + Strings.collectionToDelimitedString(clientsSettings.keySet(), ",")); - } + private Storage createClient(final String clientName, final GoogleCloudStorageClientSettings clientSettings) throws IOException { logger.debug(() -> new ParameterizedMessage("creating GCS client with client_name [{}], endpoint [{}]", clientName, clientSettings.getHost())); - final HttpTransport httpTransport = createHttpTransport(clientSettings.getHost()); + final HttpTransport httpTransport = SocketAccess.doPrivilegedIOException(() -> createHttpTransport(clientSettings.getHost())); final HttpTransportOptions httpTransportOptions = HttpTransportOptions.newBuilder() .setConnectTimeout(toTimeout(clientSettings.getConnectTimeout())) .setReadTimeout(toTimeout(clientSettings.getReadTimeout())) From 118441116c0750109e138964ee4d6dab6f27fdf3 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Mon, 11 Jun 2018 19:13:27 +0300 Subject: [PATCH 3/5] EC2 plugin lazy client ditching cached settings --- .../discovery/ec2/AwsEc2Service.java | 3 +- .../discovery/ec2/AwsEc2ServiceImpl.java | 84 +++++++++++-------- 2 files changed, 48 insertions(+), 39 deletions(-) diff --git a/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2Service.java b/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2Service.java index c001e35ad1ee6..976f1db26d173 100644 --- a/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2Service.java +++ b/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2Service.java @@ -93,8 +93,7 @@ class HostType { * client. * * @param clientSettings the new refreshed settings - * @return the old stale settings */ - Ec2ClientSettings refreshAndClearCache(Ec2ClientSettings clientSettings); + void refreshAndClearCache(Ec2ClientSettings clientSettings); } diff --git a/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2ServiceImpl.java b/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2ServiceImpl.java index d3aaa153711e4..b4b7acf2ab481 100644 --- a/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2ServiceImpl.java +++ b/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2ServiceImpl.java @@ -19,8 +19,10 @@ package org.elasticsearch.discovery.ec2; -import java.io.IOException; +import java.util.Objects; import java.util.Random; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; import com.amazonaws.ClientConfiguration; import com.amazonaws.auth.AWSCredentialsProvider; @@ -32,17 +34,18 @@ import com.amazonaws.services.ec2.AmazonEC2; import com.amazonaws.services.ec2.AmazonEC2Client; import org.apache.logging.log4j.Logger; +import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.Randomness; import org.elasticsearch.common.Strings; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.Lazy; class AwsEc2ServiceImpl extends AbstractComponent implements AwsEc2Service { public static final String EC2_METADATA_URL = "http://169.254.169.254/latest/meta-data/"; - private volatile AmazonEc2Reference clientReference; - private volatile Ec2ClientSettings clientSettings; + private final AtomicReference lazyClientReference = new AtomicReference<>(); AwsEc2ServiceImpl(Settings settings) { super(settings); @@ -109,55 +112,62 @@ static AWSCredentialsProvider buildCredentials(Logger logger, Ec2ClientSettings @Override public AmazonEc2Reference client() { - if ((clientReference != null) && clientReference.tryIncRef()) { - return clientReference; - } - synchronized (this) { - if ((clientReference != null) && clientReference.tryIncRef()) { - return clientReference; - } - if (clientSettings == null) { - throw new IllegalArgumentException("Missing ec2 client configs."); - } - final AmazonEc2Reference clientReference = new AmazonEc2Reference(buildClient(clientSettings)); - clientReference.incRef(); - this.clientReference = clientReference; - return clientReference; + final LazyAmazonEc2Reference clientReference = this.lazyClientReference.get(); + if (clientReference == null) { + throw new IllegalStateException("Missing ec2 client configs"); } + return clientReference.getOrCompute(); } - /** - * Refreshes the settings for the AmazonEC2 client. New clients will be build - * using these new settings. Old client is usable until released. On release it + * Refreshes the settings for the AmazonEC2 client. The new client will be build + * using these new settings. The old client is usable until released. On release it * will be destroyed instead of being returned to the cache. */ @Override - public synchronized Ec2ClientSettings refreshAndClearCache(Ec2ClientSettings clientSettings) { - // shutdown all unused clients - // others will shutdown on their respective release - releaseCachedClient(); - final Ec2ClientSettings prevSettings = this.clientSettings; - this.clientSettings = clientSettings; - return prevSettings; + public void refreshAndClearCache(Ec2ClientSettings clientSettings) { + final LazyAmazonEc2Reference newClient = new LazyAmazonEc2Reference(() -> buildClient(clientSettings)); + final LazyAmazonEc2Reference oldClient = this.lazyClientReference.getAndSet(newClient); + if (oldClient != null) { + oldClient.clear(); + } } @Override public void close() { - releaseCachedClient(); - } - - private synchronized void releaseCachedClient() { - if (this.clientReference == null) { - return; + final LazyAmazonEc2Reference clientReference = this.lazyClientReference.getAndSet(null); + if (clientReference != null) { + clientReference.clear(); } - // the client will shutdown when it will not be used anymore - this.clientReference.decRef(); - // clear the cached client, it will be build lazily - this.clientReference = null; // shutdown IdleConnectionReaper background thread // it will be restarted on new client usage IdleConnectionReaper.shutdown(); } + private static class LazyAmazonEc2Reference extends Lazy { + + LazyAmazonEc2Reference(Supplier supplier) { + super(() -> { + final AmazonEC2 client = Objects.requireNonNull(supplier.get()); + // wrap supplier result in a reference counted object with initial count set to 1 + final AmazonEc2Reference clientReference = new AmazonEc2Reference(client); + clientReference.incRef(); + return clientReference; + }, clientReference -> clientReference.decRef()); + } + + @Override + public AmazonEc2Reference getOrCompute() { + AmazonEc2Reference result; + try { + result = super.getOrCompute(); + } catch (final Exception e) { + throw new ElasticsearchException("This is unexpected", e); + } + // the count will be decreased by {@codee AmazonEc2Reference#close} + result.incRef(); + return result; + } + } + } From e5a9934c33923374f55d4e7ad82f92a275c54f4e Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Fri, 15 Jun 2018 21:47:38 +0300 Subject: [PATCH 4/5] polished LazyInitializable --- .../discovery/ec2/AwsEc2ServiceImpl.java | 48 ++------ .../azure/AzureStorageServiceMock.java | 2 +- .../gcs/GoogleCloudStorageService.java | 15 +-- .../org/elasticsearch/common/util/Lazy.java | 61 ---------- .../common/util/LazyInitializable.java | 108 ++++++++++++++++++ 5 files changed, 129 insertions(+), 105 deletions(-) delete mode 100644 server/src/main/java/org/elasticsearch/common/util/Lazy.java create mode 100644 server/src/main/java/org/elasticsearch/common/util/LazyInitializable.java diff --git a/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2ServiceImpl.java b/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2ServiceImpl.java index 075ecd90ebb23..67902174630ea 100644 --- a/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2ServiceImpl.java +++ b/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2ServiceImpl.java @@ -19,10 +19,8 @@ package org.elasticsearch.discovery.ec2; -import java.util.Objects; import java.util.Random; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; import com.amazonaws.ClientConfiguration; import com.amazonaws.auth.AWSCredentialsProvider; @@ -36,15 +34,17 @@ import org.apache.logging.log4j.Logger; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.Randomness; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.util.Lazy; +import org.elasticsearch.common.util.LazyInitializable; class AwsEc2ServiceImpl extends AbstractComponent implements AwsEc2Service { public static final String EC2_METADATA_URL = "http://169.254.169.254/latest/meta-data/"; - private final AtomicReference lazyClientReference = new AtomicReference<>(); + private final AtomicReference> lazyClientReference = + new AtomicReference<>(); AwsEc2ServiceImpl(Settings settings) { super(settings); @@ -111,7 +111,7 @@ static AWSCredentialsProvider buildCredentials(Logger logger, Ec2ClientSettings @Override public AmazonEc2Reference client() { - final LazyAmazonEc2Reference clientReference = this.lazyClientReference.get(); + final LazyInitializable clientReference = this.lazyClientReference.get(); if (clientReference == null) { throw new IllegalStateException("Missing ec2 client configs"); } @@ -125,48 +125,24 @@ public AmazonEc2Reference client() { */ @Override public void refreshAndClearCache(Ec2ClientSettings clientSettings) { - final LazyAmazonEc2Reference newClient = new LazyAmazonEc2Reference(() -> buildClient(clientSettings)); - final LazyAmazonEc2Reference oldClient = this.lazyClientReference.getAndSet(newClient); + final LazyInitializable newClient = new LazyInitializable<>( + () -> new AmazonEc2Reference(buildClient(clientSettings)), clientReference -> clientReference.incRef(), + clientReference -> clientReference.decRef()); + final LazyInitializable oldClient = this.lazyClientReference.getAndSet(newClient); if (oldClient != null) { - oldClient.clear(); + oldClient.reset(); } } @Override public void close() { - final LazyAmazonEc2Reference clientReference = this.lazyClientReference.getAndSet(null); + final LazyInitializable clientReference = this.lazyClientReference.getAndSet(null); if (clientReference != null) { - clientReference.clear(); + clientReference.reset(); } // shutdown IdleConnectionReaper background thread // it will be restarted on new client usage IdleConnectionReaper.shutdown(); } - private static class LazyAmazonEc2Reference extends Lazy { - - LazyAmazonEc2Reference(Supplier supplier) { - super(() -> { - final AmazonEC2 client = Objects.requireNonNull(supplier.get()); - // wrap supplier result in a reference counted object with initial count set to 1 - final AmazonEc2Reference clientReference = new AmazonEc2Reference(client); - clientReference.incRef(); - return clientReference; - }, clientReference -> clientReference.decRef()); - } - - @Override - public AmazonEc2Reference getOrCompute() { - AmazonEc2Reference result; - try { - result = super.getOrCompute(); - } catch (final Exception e) { - throw new ElasticsearchException("This is unexpected", e); - } - // the count will be decreased by {@codee AmazonEc2Reference#close} - result.incRef(); - return result; - } - } - } diff --git a/plugins/repository-azure/src/test/java/org/elasticsearch/repositories/azure/AzureStorageServiceMock.java b/plugins/repository-azure/src/test/java/org/elasticsearch/repositories/azure/AzureStorageServiceMock.java index bb6afee8f7b09..a680af06fc655 100644 --- a/plugins/repository-azure/src/test/java/org/elasticsearch/repositories/azure/AzureStorageServiceMock.java +++ b/plugins/repository-azure/src/test/java/org/elasticsearch/repositories/azure/AzureStorageServiceMock.java @@ -115,7 +115,7 @@ public Map listBlobsByPrefix(String account, String contai @Override public void writeBlob(String account, String container, String blobName, InputStream inputStream, long blobSize) - throws URISyntaxException, StorageException, FileAlreadyExistsExceeption { + throws URISyntaxException, StorageException, FileAlreadyExistsException { if (blobs.containsKey(blobName)) { throw new FileAlreadyExistsException(blobName); } diff --git a/plugins/repository-gcs/src/main/java/org/elasticsearch/repositories/gcs/GoogleCloudStorageService.java b/plugins/repository-gcs/src/main/java/org/elasticsearch/repositories/gcs/GoogleCloudStorageService.java index 621b611dec1a1..b24674da174c3 100644 --- a/plugins/repository-gcs/src/main/java/org/elasticsearch/repositories/gcs/GoogleCloudStorageService.java +++ b/plugins/repository-gcs/src/main/java/org/elasticsearch/repositories/gcs/GoogleCloudStorageService.java @@ -34,7 +34,7 @@ import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.common.util.Lazy; +import org.elasticsearch.common.util.LazyInitializable; import java.io.IOException; import java.net.HttpURLConnection; @@ -52,7 +52,7 @@ public class GoogleCloudStorageService extends AbstractComponent { * Dictionary of client instances. Client instances are built lazily from the * latest settings. */ - private AtomicReference>> clientsCache = new AtomicReference<>(emptyMap()); + private final AtomicReference>> clientsCache = new AtomicReference<>(emptyMap()); public GoogleCloudStorageService(final Settings settings) { super(settings); @@ -67,14 +67,15 @@ public GoogleCloudStorageService(final Settings settings) { */ public synchronized void refreshAndClearCache(Map clientsSettings) { // build the new lazy clients - final MapBuilder> newClientsCache = MapBuilder.newMapBuilder(); + final MapBuilder> newClientsCache = MapBuilder.newMapBuilder(); for (final Map.Entry entry : clientsSettings.entrySet()) { - newClientsCache.put(entry.getKey(), new Lazy(() -> createClient(entry.getKey(), entry.getValue()))); + newClientsCache.put(entry.getKey(), + new LazyInitializable(() -> createClient(entry.getKey(), entry.getValue()))); } // make the new clients available - final Map> oldClientCache = this.clientsCache.getAndSet(newClientsCache.immutableMap()); + final Map> oldClientCache = clientsCache.getAndSet(newClientsCache.immutableMap()); // release old clients - oldClientCache.values().forEach(Lazy::clear); + oldClientCache.values().forEach(LazyInitializable::reset); } /** @@ -89,7 +90,7 @@ public synchronized void refreshAndClearCache(Map lazyClient = clientsCache.get().get(clientName); + final LazyInitializable lazyClient = clientsCache.get().get(clientName); if (lazyClient == null) { throw new IllegalArgumentException("Unknown client name [" + clientName + "]. Existing client configs: " + Strings.collectionToDelimitedString(clientsCache.get().keySet(), ",")); diff --git a/server/src/main/java/org/elasticsearch/common/util/Lazy.java b/server/src/main/java/org/elasticsearch/common/util/Lazy.java deleted file mode 100644 index 221d364aae8a1..0000000000000 --- a/server/src/main/java/org/elasticsearch/common/util/Lazy.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.elasticsearch.common.util; - -import org.elasticsearch.common.CheckedSupplier; - -import java.util.Objects; -import java.util.function.Consumer; - -public class Lazy { - - private final CheckedSupplier supplier; - private final Consumer finalizer; - private volatile T value; - - public Lazy(CheckedSupplier supplier) { - this(supplier, v -> {}); - } - - public Lazy(CheckedSupplier supplier, Consumer finalizer) { - this.supplier = supplier; - this.finalizer = finalizer; - } - - public T getOrCompute() throws E { - final T result = value; // Read volatile just once... - return result == null ? maybeCompute(supplier) : result; - } - - public synchronized void clear() { - if (value != null) { - finalizer.accept(value); - value = null; - } - } - - private synchronized T maybeCompute(CheckedSupplier supplier) throws E { - if (value == null) { - value = Objects.requireNonNull(supplier.get()); - } - return value; - } - -} diff --git a/server/src/main/java/org/elasticsearch/common/util/LazyInitializable.java b/server/src/main/java/org/elasticsearch/common/util/LazyInitializable.java new file mode 100644 index 0000000000000..328f887b794b3 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/common/util/LazyInitializable.java @@ -0,0 +1,108 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.util; + +import org.elasticsearch.common.CheckedSupplier; + +import java.util.Objects; +import java.util.function.Consumer; + +/** + * Encapsulates a {@link CheckedSupplier} which is lazily invoked once on the + * first call to {@code #getOrCompute()}. The value which the + * supplier returns is memorized and will be served until + * {@code #reset()} is called. Each value returned by {@code #getOrCompute()}, + * newly minted or cached, will be passed to the primer + * {@link Consumer}. On {@code #reset()} the value will be passed to the + * finalizer {@code Consumer} and the next {@code #getOrCompute()} + * will regenerate the value. + */ +public final class LazyInitializable { + + private final CheckedSupplier supplier; + private final Consumer primer; + private final Consumer finalizer; + private volatile T value; + + /** + * Creates the simple LazyInitializable instance. + * + * @param supplier + * The {@code CheckedSupplier} to generate values which will be + * served on {@code #getOrCompute()} invocations. + */ + public LazyInitializable(CheckedSupplier supplier) { + this(supplier, v -> {}, v -> {}); + } + + /** + * Creates the complete LazyInitializable instance. + * + * @param supplier + * The {@code CheckedSupplier} to generate values which will be + * served on {@code #getOrCompute()} invocations. + * @param primer + * A {@code Consumer} which is called on each value, newly forged or + * stale, that is returned by {@code #getOrCompute()} + * @param finalizer + * A {@code Consumer} which is invoked on the value that will be + * erased when calling {@code #reset()} + */ + public LazyInitializable(CheckedSupplier supplier, Consumer primer, Consumer finalizer) { + this.supplier = supplier; + this.primer = primer; + this.finalizer = finalizer; + } + + /** + * Returns a value that was created by supplier. The value might + * have been previously created, if not it will be created now, thread safe of + * course. + */ + public T getOrCompute() throws E { + final T readOnce = value; // Read volatile just once... + final T result = readOnce == null ? maybeCompute(supplier) : readOnce; + primer.accept(result); + return result; + } + + /** + * Clears the value, if it has been previously created by calling + * {@code #getOrCompute()}. The finalizer will be called on this + * value. The next call to {@code #getOrCompute()} will recreate the value. + */ + public synchronized void reset() { + if (value != null) { + finalizer.accept(value); + value = null; + } + } + + /** + * Creates a new value thread safely. + */ + private synchronized T maybeCompute(CheckedSupplier supplier) throws E { + if (value == null) { + value = Objects.requireNonNull(supplier.get()); + } + return value; + } + +} From 86b3ebbc8709eb091186321aa71b715dc0abbdeb Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Sat, 16 Jun 2018 20:42:44 +0300 Subject: [PATCH 5/5] Renames --- .../common/util/LazyInitializable.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/common/util/LazyInitializable.java b/server/src/main/java/org/elasticsearch/common/util/LazyInitializable.java index 328f887b794b3..6675dccf2c3d2 100644 --- a/server/src/main/java/org/elasticsearch/common/util/LazyInitializable.java +++ b/server/src/main/java/org/elasticsearch/common/util/LazyInitializable.java @@ -29,16 +29,16 @@ * first call to {@code #getOrCompute()}. The value which the * supplier returns is memorized and will be served until * {@code #reset()} is called. Each value returned by {@code #getOrCompute()}, - * newly minted or cached, will be passed to the primer + * newly minted or cached, will be passed to the onGet * {@link Consumer}. On {@code #reset()} the value will be passed to the - * finalizer {@code Consumer} and the next {@code #getOrCompute()} + * onReset {@code Consumer} and the next {@code #getOrCompute()} * will regenerate the value. */ public final class LazyInitializable { private final CheckedSupplier supplier; - private final Consumer primer; - private final Consumer finalizer; + private final Consumer onGet; + private final Consumer onReset; private volatile T value; /** @@ -58,17 +58,17 @@ public LazyInitializable(CheckedSupplier supplier) { * @param supplier * The {@code CheckedSupplier} to generate values which will be * served on {@code #getOrCompute()} invocations. - * @param primer + * @param onGet * A {@code Consumer} which is called on each value, newly forged or * stale, that is returned by {@code #getOrCompute()} - * @param finalizer + * @param onReset * A {@code Consumer} which is invoked on the value that will be * erased when calling {@code #reset()} */ public LazyInitializable(CheckedSupplier supplier, Consumer primer, Consumer finalizer) { this.supplier = supplier; - this.primer = primer; - this.finalizer = finalizer; + this.onGet = primer; + this.onReset = finalizer; } /** @@ -79,18 +79,18 @@ public LazyInitializable(CheckedSupplier supplier, Consumer primer, Con public T getOrCompute() throws E { final T readOnce = value; // Read volatile just once... final T result = readOnce == null ? maybeCompute(supplier) : readOnce; - primer.accept(result); + onGet.accept(result); return result; } /** * Clears the value, if it has been previously created by calling - * {@code #getOrCompute()}. The finalizer will be called on this + * {@code #getOrCompute()}. The onReset will be called on this * value. The next call to {@code #getOrCompute()} will recreate the value. */ public synchronized void reset() { if (value != null) { - finalizer.accept(value); + onReset.accept(value); value = null; } }