diff --git a/common/common/src/main/java/io/helidon/common/LazyValue.java b/common/common/src/main/java/io/helidon/common/LazyValue.java new file mode 100644 index 00000000000..df87395aa4e --- /dev/null +++ b/common/common/src/main/java/io/helidon/common/LazyValue.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.common; + +import java.util.function.Supplier; + +/** + * A typed supplier that wraps another supplier and only retrieves the value on the first + * request to {@link #get()}, caching the value for all subsequent invocations. + *

+ * Helidon implementations obtained through {@link #create(java.util.function.Supplier)} + * and {@link #create(Object)} are guaranteed to be thread safe. + * + * @param type of the provided object + */ +@FunctionalInterface +public interface LazyValue extends Supplier { + /** + * Create a lazy value from a supplier. + * @param supplier supplier to get the value from + * @param type of the value + * @return a lazy value that will obtain the value from supplier on first call to {@link #get()} + */ + static LazyValue create(Supplier supplier) { + return new LazyValueImpl<>(supplier); + } + + /** + * Create a lazy value from a value. + * + * @param value actual value to return + * @param type of the value + * @return a lazy value that will always return the value provided + */ + static LazyValue create(T value) { + return () -> value; + } + +} diff --git a/common/common/src/main/java/io/helidon/common/LazyValueImpl.java b/common/common/src/main/java/io/helidon/common/LazyValueImpl.java new file mode 100644 index 00000000000..5df8792cb92 --- /dev/null +++ b/common/common/src/main/java/io/helidon/common/LazyValueImpl.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.common; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; + +class LazyValueImpl implements LazyValue { + private final Lock theLock = new ReentrantLock(); + + private volatile T value; + + private Supplier delegate; + private volatile boolean loaded; + + LazyValueImpl(Supplier supplier) { + this.delegate = supplier; + } + + @Override + public T get() { + if (loaded) { + return value; + } + + // not loaded (probably) + theLock.lock(); + + try { + if (loaded) { + return value; + } + value = delegate.get(); + loaded = true; + delegate = null; + } finally { + theLock.unlock(); + } + + return value; + } +} diff --git a/common/common/src/test/java/io/helidon/common/LazyValueTest.java b/common/common/src/test/java/io/helidon/common/LazyValueTest.java new file mode 100644 index 00000000000..1d86d6d4662 --- /dev/null +++ b/common/common/src/test/java/io/helidon/common/LazyValueTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.common; + +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +/** + * Unit test for {@link LazyValue}. + */ +class LazyValueTest { + + @Test + void testValue() { + String text = "Helidon"; + LazyValue value = LazyValue.create(text); + + String s = value.get(); + assertThat(s, is(text)); + } + + @Test + void testSupplier() { + String text = "Helidon"; + AtomicInteger called = new AtomicInteger(); + + LazyValue value = LazyValue.create(() -> { + called.incrementAndGet(); + return text; + }); + + String s = value.get(); + assertThat(s, is(text)); + assertThat(called.get(), is(1)); + + s = value.get(); + assertThat(s, is(text)); + assertThat(called.get(), is(1)); + } + + @Test + void testSupplierParallel() { + String text = "Helidon"; + AtomicInteger called = new AtomicInteger(); + AtomicInteger threadsStarted = new AtomicInteger(); + + LazyValue value = LazyValue.create(() -> { + called.incrementAndGet(); + return text; + }); + + Errors.Collector errors = Errors.collector(); + + int threadCount = 20; + CyclicBarrier barrier = new CyclicBarrier(threadCount); + Thread[] testingThreads = new Thread[threadCount]; + for (int i = 0; i < testingThreads.length; i++) { + testingThreads[i] = new Thread(() -> { + threadsStarted.incrementAndGet(); + try { + barrier.await(); + } catch (Exception e) { + errors.fatal("Failed to start, barrier failed: " + e.getMessage()); + } + String s = value.get(); + if (!text.equals(s)) { + errors.fatal("Got wrong value. Expected " + text + ", but got: " + s); + } + }); + } + + for (Thread testingThread : testingThreads) { + testingThread.start(); + } + + for (Thread testingThread : testingThreads) { + try { + testingThread.join(); + } catch (InterruptedException ignored) { + } + } + + errors.collect().checkValid(); + assertThat(called.get(), is(1)); + assertThat(threadsStarted.get(), is(threadCount)); + } +} \ No newline at end of file diff --git a/common/configurable/src/main/java/io/helidon/common/configurable/ScheduledThreadPoolSupplier.java b/common/configurable/src/main/java/io/helidon/common/configurable/ScheduledThreadPoolSupplier.java index 30aab6859d2..f4a7b568052 100644 --- a/common/configurable/src/main/java/io/helidon/common/configurable/ScheduledThreadPoolSupplier.java +++ b/common/configurable/src/main/java/io/helidon/common/configurable/ScheduledThreadPoolSupplier.java @@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; +import io.helidon.common.LazyValue; import io.helidon.common.context.Contexts; import io.helidon.config.Config; @@ -41,7 +42,7 @@ public final class ScheduledThreadPoolSupplier implements Supplier lazyValue = LazyValue.create(() -> Contexts.wrap(getThreadPool())); private ScheduledThreadPoolSupplier(Builder builder) { this.corePoolSize = builder.corePoolSize; @@ -82,18 +83,18 @@ public static ScheduledThreadPoolSupplier create() { ScheduledThreadPoolExecutor getThreadPool() { ScheduledThreadPoolExecutor result; result = new ScheduledThreadPoolExecutor(corePoolSize, - new ThreadFactory() { - private final AtomicInteger value = new AtomicInteger(); - - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(null, - r, - threadNamePrefix + value.incrementAndGet()); - t.setDaemon(isDaemon); - return t; - } - }); + new ThreadFactory() { + private final AtomicInteger value = new AtomicInteger(); + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(null, + r, + threadNamePrefix + value.incrementAndGet()); + t.setDaemon(isDaemon); + return t; + } + }); if (prestart) { result.prestartAllCoreThreads(); } @@ -101,11 +102,8 @@ public Thread newThread(Runnable r) { } @Override - public synchronized ScheduledExecutorService get() { - if (null == instance) { - instance = Contexts.wrap(getThreadPool()); - } - return instance; + public ScheduledExecutorService get() { + return lazyValue.get(); } /** diff --git a/common/configurable/src/main/java/io/helidon/common/configurable/ThreadPoolSupplier.java b/common/configurable/src/main/java/io/helidon/common/configurable/ThreadPoolSupplier.java index 0e0b0b48a5d..3adcf95cf1e 100644 --- a/common/configurable/src/main/java/io/helidon/common/configurable/ThreadPoolSupplier.java +++ b/common/configurable/src/main/java/io/helidon/common/configurable/ThreadPoolSupplier.java @@ -23,6 +23,7 @@ import java.util.function.Supplier; import java.util.logging.Logger; +import io.helidon.common.LazyValue; import io.helidon.common.context.Contexts; import io.helidon.config.Config; @@ -56,7 +57,7 @@ public final class ThreadPoolSupplier implements Supplier { private final int growthThreshold; private final int growthRate; private final ThreadPool.RejectionHandler rejectionHandler; - private volatile ExecutorService instance; + private final LazyValue lazyValue = LazyValue.create(() -> Contexts.wrap(getThreadPool())); private ThreadPoolSupplier(Builder builder) { this.corePoolSize = builder.corePoolSize; @@ -89,7 +90,7 @@ public static Builder builder() { */ public static ThreadPoolSupplier create(Config config) { return builder().config(config) - .build(); + .build(); } /** @@ -120,11 +121,8 @@ ThreadPool getThreadPool() { } @Override - public synchronized ExecutorService get() { - if (null == instance) { - instance = Contexts.wrap(getThreadPool()); - } - return instance; + public ExecutorService get() { + return lazyValue.get(); } /** @@ -357,7 +355,8 @@ public Builder prestart(boolean prestart) { *

  • there are no idle threads, and
  • *
  • the number of tasks in the queue exceeds the {@code growthThreshold}
  • * - *

    For example, a rate of 20 means that while these conditions are met one thread will be added for every 5 submitted + *

    For example, a rate of 20 means that while these conditions are met one thread will be added for every 5 + * submitted * tasks. *

    A rate of 0 selects the default {@link ThreadPoolExecutor} growth behavior: a thread is added only when a * submitted task is rejected because the queue is full. diff --git a/common/context/src/main/java/io/helidon/common/context/ListContext.java b/common/context/src/main/java/io/helidon/common/context/ListContext.java index 9bbcec6af77..78eb93e021d 100644 --- a/common/context/src/main/java/io/helidon/common/context/ListContext.java +++ b/common/context/src/main/java/io/helidon/common/context/ListContext.java @@ -27,6 +27,8 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Supplier; +import io.helidon.common.LazyValue; + /** * A {@link Context} implementation with deque registry. */ @@ -140,17 +142,15 @@ private void registerItem(RegisteredItem item) { } } - @SuppressWarnings("unchecked") void register(T instance) { Objects.requireNonNull(instance, "Parameter 'instance' is null!"); - registerItem(new RegisteredInstance(instance)); + registerItem(new RegisteredInstance<>(instance)); } - @SuppressWarnings("unchecked") void supply(Class type, Supplier supplier) { Objects.requireNonNull(type, "Parameter 'type' is null!"); Objects.requireNonNull(supplier, "Parameter 'supplier' is null!"); - registerItem(new RegisteredSupplier(type, supplier)); + registerItem(new RegisteredSupplier<>(type, supplier)); } T get(Class type) { @@ -173,26 +173,16 @@ T get(Class type) { private static class RegisteredSupplier implements RegisteredItem { private final Class type; - private final Supplier supplier; - private volatile boolean missing = true; - private volatile T instance; + private final LazyValue value; RegisteredSupplier(Class type, Supplier supplier) { this.type = type; - this.supplier = supplier; + this.value = LazyValue.create(supplier); } @Override public T get() { - if (missing) { - synchronized (this) { - if (missing) { - missing = false; - instance = supplier.get(); - } - } - } - return instance; + return value.get(); } @Override