All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mycila.inject.scope.ConcurrentSingleton Maven / Gradle / Ivy

/**
 * Copyright (C) 2010 Mycila 
 *
 * 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 com.mycila.inject.scope;

import com.google.inject.Binding;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.mycila.inject.MycilaGuiceException;
import com.mycila.inject.annotation.Jsr250Singleton;

import javax.annotation.PreDestroy;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.FutureTask;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

@Jsr250Singleton
public final class ConcurrentSingleton extends MycilaScope {
    final long expirationDelay;

    public ConcurrentSingleton(long expirationDelay, TimeUnit unit) {
        this.expirationDelay = unit.toMillis(expirationDelay);
    }

    private final FutureInjector futureInjector = new FutureInjector();
    private final ExecutorService executor = new ThreadPoolExecutor(
            0, Runtime.getRuntime().availableProcessors() * 10,
            5, TimeUnit.SECONDS,
            new SynchronousQueue(),
            new DefaultThreadFactory("@" + ConcurrentSingleton.class.getSimpleName() + "-Thread-"),
            new ThreadPoolExecutor.DiscardOldestPolicy());

    @Inject
    public void initFuture(Injector injector) {
        futureInjector.setInjector(injector);
    }

    @PreDestroy
    public void shutdown() {
        executor.shutdown();
    }

    @Override
    public  Provider scope(final Key key, final Provider unscoped) {
        // Create a monitoring thread for this singleton.
        // This thread will wait for the key to be available and call the provider to get the singleton.
        // This thread must also be non bloquant so that it can be interrupted if the program finishes
        executor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    // this thread will expire if not ended within the given timeout
                    final long expirationTime = System.currentTimeMillis() + expirationDelay;
                    while (!Thread.currentThread().isInterrupted() && System.currentTimeMillis() < expirationTime) {
                        final Injector injector = futureInjector.waitAndGet(500, TimeUnit.MILLISECONDS).get();
                        if (injector == null) {
                            // May not be ready now. Retry later.
                            Thread.sleep(500);
                        } else {
                            final Binding binding = injector.getExistingBinding(key);
                            if (binding == null) {
                                // wait: perhaps it is not yet available to be constructed
                                Thread.sleep(500);
                            } else {
                                try {
                                    // call the provider it load the singleton in this thread
                                    binding.getProvider().get();
                                } catch (Throwable ignored) {
                                    // completely ignore the exception: since this provider calls the FutureProvider,
                                    // the FutureTask will memoize the exception and rethrow it for subsequent calls
                                    // not within this loader thread
                                }
                                return;
                            }
                        }
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        });
        // Future task that will memoize the provided singleton.
        // The task will be run either in the caller thread, or by the monitoring thread
        return new FutureProvider(key, unscoped);
    }

    private static final class FutureInjector {
        private volatile WeakReference injector = new WeakReference(null);
        private final CountDownLatch injectorAvailable = new CountDownLatch(1);

        public void setInjector(Injector injector) {
            if (this.injector.get() != null) return;
            this.injector = new WeakReference(injector);
            injectorAvailable.countDown();
        }

        public Reference waitAndGet(long timeout, TimeUnit unit) throws InterruptedException {
            // We need to apply a timeout in case the user forgot to request injection on the scope to not block the threads.
            // If the injector is not ready within this timeout, we consider it as inexisting
            injectorAvailable.await(timeout, unit);
            return injector;
        }
    }

    private static final class FutureProvider extends FutureTask implements Provider {
        private final Key key;

        private FutureProvider(Key key, final Provider unscoped) {
            super(new Callable() {
                @Override
                public T call() throws Exception {
                    return unscoped.get();
                }
            });
            this.key = key;
        }

        @Override
        public T get() {
            try {
                if (!isDone()) run();
                return super.get();
            } catch (ExecutionException e) {
                throw MycilaGuiceException.runtime(e);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw MycilaGuiceException.runtime(e);
            }
        }

        @Override
        public String toString() {
            return "FutureProvider[" + key + "]";
        }
    }

    private static final class DefaultThreadFactory implements ThreadFactory {
        private final ThreadGroup group;
        private final AtomicLong threadNumber = new AtomicLong();
        private final String namePrefix;

        private DefaultThreadFactory(String namePrefix) {
            SecurityManager s = System.getSecurityManager();
            this.group = s != null ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
            this.namePrefix = namePrefix;
        }

        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy