001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.devtools.restart;
018
019import java.beans.Introspector;
020import java.lang.Thread.UncaughtExceptionHandler;
021import java.lang.reflect.Field;
022import java.net.URL;
023import java.util.Arrays;
024import java.util.Collection;
025import java.util.HashMap;
026import java.util.LinkedHashSet;
027import java.util.LinkedList;
028import java.util.List;
029import java.util.Map;
030import java.util.Set;
031import java.util.concurrent.BlockingDeque;
032import java.util.concurrent.Callable;
033import java.util.concurrent.CopyOnWriteArrayList;
034import java.util.concurrent.LinkedBlockingDeque;
035import java.util.concurrent.ThreadFactory;
036import java.util.concurrent.locks.Lock;
037import java.util.concurrent.locks.ReentrantLock;
038
039import org.apache.commons.logging.Log;
040import org.apache.commons.logging.LogFactory;
041
042import org.springframework.beans.CachedIntrospectionResults;
043import org.springframework.beans.factory.ObjectFactory;
044import org.springframework.boot.SpringApplication;
045import org.springframework.boot.devtools.restart.FailureHandler.Outcome;
046import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles;
047import org.springframework.boot.devtools.restart.classloader.RestartClassLoader;
048import org.springframework.boot.logging.DeferredLog;
049import org.springframework.boot.system.JavaVersion;
050import org.springframework.cglib.core.ClassNameReader;
051import org.springframework.context.ConfigurableApplicationContext;
052import org.springframework.context.support.GenericApplicationContext;
053import org.springframework.core.ResolvableType;
054import org.springframework.core.annotation.AnnotationUtils;
055import org.springframework.core.io.ResourceLoader;
056import org.springframework.util.Assert;
057import org.springframework.util.ReflectionUtils;
058
059/**
060 * Allows a running application to be restarted with an updated classpath. The restarter
061 * works by creating a new application ClassLoader that is split into two parts. The top
062 * part contains static URLs that don't change (for example 3rd party libraries and Spring
063 * Boot itself) and the bottom part contains URLs where classes and resources might be
064 * updated.
065 * <p>
066 * The Restarter should be {@link #initialize(String[]) initialized} early to ensure that
067 * classes are loaded multiple times. Mostly the {@link RestartApplicationListener} can be
068 * relied upon to perform initialization, however, you may need to call
069 * {@link #initialize(String[])} directly if your SpringApplication arguments are not
070 * identical to your main method arguments.
071 * <p>
072 * By default, applications running in an IDE (i.e. those not packaged as "fat jars") will
073 * automatically detect URLs that can change. It's also possible to manually configure
074 * URLs or class file updates for remote restart scenarios.
075 *
076 * @author Phillip Webb
077 * @author Andy Wilkinson
078 * @since 1.3.0
079 * @see RestartApplicationListener
080 * @see #initialize(String[])
081 * @see #getInstance()
082 * @see #restart()
083 */
084public class Restarter {
085
086        private static final Object INSTANCE_MONITOR = new Object();
087
088        private static final String[] NO_ARGS = {};
089
090        private static Restarter instance;
091
092        private final Set<URL> urls = new LinkedHashSet<>();
093
094        private final ClassLoaderFiles classLoaderFiles = new ClassLoaderFiles();
095
096        private final Map<String, Object> attributes = new HashMap<>();
097
098        private final BlockingDeque<LeakSafeThread> leakSafeThreads = new LinkedBlockingDeque<>();
099
100        private final Lock stopLock = new ReentrantLock();
101
102        private final Object monitor = new Object();
103
104        private Log logger = new DeferredLog();
105
106        private final boolean forceReferenceCleanup;
107
108        private boolean enabled = true;
109
110        private URL[] initialUrls;
111
112        private final String mainClassName;
113
114        private final ClassLoader applicationClassLoader;
115
116        private final String[] args;
117
118        private final UncaughtExceptionHandler exceptionHandler;
119
120        private boolean finished = false;
121
122        private final List<ConfigurableApplicationContext> rootContexts = new CopyOnWriteArrayList<>();
123
124        /**
125         * Internal constructor to create a new {@link Restarter} instance.
126         * @param thread the source thread
127         * @param args the application arguments
128         * @param forceReferenceCleanup if soft/weak reference cleanup should be forced
129         * @param initializer the restart initializer
130         * @see #initialize(String[])
131         */
132        protected Restarter(Thread thread, String[] args, boolean forceReferenceCleanup,
133                        RestartInitializer initializer) {
134                Assert.notNull(thread, "Thread must not be null");
135                Assert.notNull(args, "Args must not be null");
136                Assert.notNull(initializer, "Initializer must not be null");
137                if (this.logger.isDebugEnabled()) {
138                        this.logger.debug("Creating new Restarter for thread " + thread);
139                }
140                SilentExitExceptionHandler.setup(thread);
141                this.forceReferenceCleanup = forceReferenceCleanup;
142                this.initialUrls = initializer.getInitialUrls(thread);
143                this.mainClassName = getMainClassName(thread);
144                this.applicationClassLoader = thread.getContextClassLoader();
145                this.args = args;
146                this.exceptionHandler = thread.getUncaughtExceptionHandler();
147                this.leakSafeThreads.add(new LeakSafeThread());
148        }
149
150        private String getMainClassName(Thread thread) {
151                try {
152                        return new MainMethod(thread).getDeclaringClassName();
153                }
154                catch (Exception ex) {
155                        return null;
156                }
157        }
158
159        protected void initialize(boolean restartOnInitialize) {
160                preInitializeLeakyClasses();
161                if (this.initialUrls != null) {
162                        this.urls.addAll(Arrays.asList(this.initialUrls));
163                        if (restartOnInitialize) {
164                                this.logger.debug("Immediately restarting application");
165                                immediateRestart();
166                        }
167                }
168        }
169
170        private void immediateRestart() {
171                try {
172                        getLeakSafeThread().callAndWait(() -> {
173                                start(FailureHandler.NONE);
174                                cleanupCaches();
175                                return null;
176                        });
177                }
178                catch (Exception ex) {
179                        this.logger.warn("Unable to initialize restarter", ex);
180                }
181                SilentExitExceptionHandler.exitCurrentThread();
182        }
183
184        /**
185         * CGLIB has a private exception field which needs to initialized early to ensure that
186         * the stacktrace doesn't retain a reference to the RestartClassLoader.
187         */
188        private void preInitializeLeakyClasses() {
189                try {
190                        Class<?> readerClass = ClassNameReader.class;
191                        Field field = readerClass.getDeclaredField("EARLY_EXIT");
192                        field.setAccessible(true);
193                        ((Throwable) field.get(null)).fillInStackTrace();
194                }
195                catch (Exception ex) {
196                        this.logger.warn("Unable to pre-initialize classes", ex);
197                }
198        }
199
200        /**
201         * Set if restart support is enabled.
202         * @param enabled if restart support is enabled
203         */
204        private void setEnabled(boolean enabled) {
205                this.enabled = enabled;
206        }
207
208        /**
209         * Add additional URLs to be includes in the next restart.
210         * @param urls the urls to add
211         */
212        public void addUrls(Collection<URL> urls) {
213                Assert.notNull(urls, "Urls must not be null");
214                this.urls.addAll(urls);
215        }
216
217        /**
218         * Add additional {@link ClassLoaderFiles} to be included in the next restart.
219         * @param classLoaderFiles the files to add
220         */
221        public void addClassLoaderFiles(ClassLoaderFiles classLoaderFiles) {
222                Assert.notNull(classLoaderFiles, "ClassLoaderFiles must not be null");
223                this.classLoaderFiles.addAll(classLoaderFiles);
224        }
225
226        /**
227         * Return a {@link ThreadFactory} that can be used to create leak safe threads.
228         * @return a leak safe thread factory
229         */
230        public ThreadFactory getThreadFactory() {
231                return new LeakSafeThreadFactory();
232        }
233
234        /**
235         * Restart the running application.
236         */
237        public void restart() {
238                restart(FailureHandler.NONE);
239        }
240
241        /**
242         * Restart the running application.
243         * @param failureHandler a failure handler to deal with application that doesn't start
244         */
245        public void restart(FailureHandler failureHandler) {
246                if (!this.enabled) {
247                        this.logger.debug("Application restart is disabled");
248                        return;
249                }
250                this.logger.debug("Restarting application");
251                getLeakSafeThread().call(() -> {
252                        Restarter.this.stop();
253                        Restarter.this.start(failureHandler);
254                        return null;
255                });
256        }
257
258        /**
259         * Start the application.
260         * @param failureHandler a failure handler for application that won't start
261         * @throws Exception in case of errors
262         */
263        protected void start(FailureHandler failureHandler) throws Exception {
264                do {
265                        Throwable error = doStart();
266                        if (error == null) {
267                                return;
268                        }
269                        if (failureHandler.handle(error) == Outcome.ABORT) {
270                                return;
271                        }
272                }
273                while (true);
274        }
275
276        private Throwable doStart() throws Exception {
277                Assert.notNull(this.mainClassName, "Unable to find the main class to restart");
278                URL[] urls = this.urls.toArray(new URL[0]);
279                ClassLoaderFiles updatedFiles = new ClassLoaderFiles(this.classLoaderFiles);
280                ClassLoader classLoader = new RestartClassLoader(this.applicationClassLoader,
281                                urls, updatedFiles, this.logger);
282                if (this.logger.isDebugEnabled()) {
283                        this.logger.debug("Starting application " + this.mainClassName + " with URLs "
284                                        + Arrays.asList(urls));
285                }
286                return relaunch(classLoader);
287        }
288
289        /**
290         * Relaunch the application using the specified classloader.
291         * @param classLoader the classloader to use
292         * @return any exception that caused the launch to fail or {@code null}
293         * @throws Exception in case of errors
294         */
295        protected Throwable relaunch(ClassLoader classLoader) throws Exception {
296                RestartLauncher launcher = new RestartLauncher(classLoader, this.mainClassName,
297                                this.args, this.exceptionHandler);
298                launcher.start();
299                launcher.join();
300                return launcher.getError();
301        }
302
303        /**
304         * Stop the application.
305         * @throws Exception in case of errors
306         */
307        protected void stop() throws Exception {
308                this.logger.debug("Stopping application");
309                this.stopLock.lock();
310                try {
311                        for (ConfigurableApplicationContext context : this.rootContexts) {
312                                context.close();
313                                this.rootContexts.remove(context);
314                        }
315                        cleanupCaches();
316                        if (this.forceReferenceCleanup) {
317                                forceReferenceCleanup();
318                        }
319                }
320                finally {
321                        this.stopLock.unlock();
322                }
323                System.gc();
324                System.runFinalization();
325        }
326
327        private void cleanupCaches() throws Exception {
328                Introspector.flushCaches();
329                cleanupKnownCaches();
330        }
331
332        private void cleanupKnownCaches() throws Exception {
333                // Whilst not strictly necessary it helps to cleanup soft reference caches
334                // early rather than waiting for memory limits to be reached
335                ResolvableType.clearCache();
336                cleanCachedIntrospectionResultsCache();
337                ReflectionUtils.clearCache();
338                clearAnnotationUtilsCache();
339                if (!JavaVersion.getJavaVersion().isEqualOrNewerThan(JavaVersion.NINE)) {
340                        clear("com.sun.naming.internal.ResourceManager", "propertiesCache");
341                }
342        }
343
344        private void cleanCachedIntrospectionResultsCache() throws Exception {
345                clear(CachedIntrospectionResults.class, "acceptedClassLoaders");
346                clear(CachedIntrospectionResults.class, "strongClassCache");
347                clear(CachedIntrospectionResults.class, "softClassCache");
348        }
349
350        private void clearAnnotationUtilsCache() throws Exception {
351                try {
352                        AnnotationUtils.clearCache();
353                }
354                catch (Throwable ex) {
355                        clear(AnnotationUtils.class, "findAnnotationCache");
356                        clear(AnnotationUtils.class, "annotatedInterfaceCache");
357                }
358        }
359
360        private void clear(String className, String fieldName) {
361                try {
362                        clear(Class.forName(className), fieldName);
363                }
364                catch (Exception ex) {
365                        if (this.logger.isDebugEnabled()) {
366                                this.logger.debug("Unable to clear field " + className + " " + fieldName,
367                                                ex);
368                        }
369                }
370        }
371
372        private void clear(Class<?> type, String fieldName) throws Exception {
373                try {
374                        Field field = type.getDeclaredField(fieldName);
375                        field.setAccessible(true);
376                        Object instance = field.get(null);
377                        if (instance instanceof Set) {
378                                ((Set<?>) instance).clear();
379                        }
380                        if (instance instanceof Map) {
381                                ((Map<?, ?>) instance).keySet().removeIf(this::isFromRestartClassLoader);
382                        }
383                }
384                catch (Exception ex) {
385                        if (this.logger.isDebugEnabled()) {
386                                this.logger.debug("Unable to clear field " + type + " " + fieldName, ex);
387                        }
388                }
389        }
390
391        private boolean isFromRestartClassLoader(Object object) {
392                return (object instanceof Class
393                                && ((Class<?>) object).getClassLoader() instanceof RestartClassLoader);
394        }
395
396        /**
397         * Cleanup any soft/weak references by forcing an {@link OutOfMemoryError} error.
398         */
399        private void forceReferenceCleanup() {
400                try {
401                        final List<long[]> memory = new LinkedList<>();
402                        while (true) {
403                                memory.add(new long[102400]);
404                        }
405                }
406                catch (OutOfMemoryError ex) {
407                        // Expected
408                }
409        }
410
411        /**
412         * Called to finish {@link Restarter} initialization when application logging is
413         * available.
414         */
415        void finish() {
416                synchronized (this.monitor) {
417                        if (!isFinished()) {
418                                this.logger = DeferredLog.replay(this.logger,
419                                                LogFactory.getLog(getClass()));
420                                this.finished = true;
421                        }
422                }
423        }
424
425        boolean isFinished() {
426                synchronized (this.monitor) {
427                        return this.finished;
428                }
429        }
430
431        void prepare(ConfigurableApplicationContext applicationContext) {
432                if (applicationContext != null && applicationContext.getParent() != null) {
433                        return;
434                }
435                if (applicationContext instanceof GenericApplicationContext) {
436                        prepare((GenericApplicationContext) applicationContext);
437                }
438                this.rootContexts.add(applicationContext);
439        }
440
441        void remove(ConfigurableApplicationContext applicationContext) {
442                if (applicationContext != null) {
443                        this.rootContexts.remove(applicationContext);
444                }
445        }
446
447        private void prepare(GenericApplicationContext applicationContext) {
448                ResourceLoader resourceLoader = new ClassLoaderFilesResourcePatternResolver(
449                                applicationContext, this.classLoaderFiles);
450                applicationContext.setResourceLoader(resourceLoader);
451        }
452
453        private LeakSafeThread getLeakSafeThread() {
454                try {
455                        return this.leakSafeThreads.takeFirst();
456                }
457                catch (InterruptedException ex) {
458                        Thread.currentThread().interrupt();
459                        throw new IllegalStateException(ex);
460                }
461        }
462
463        public Object getOrAddAttribute(String name, final ObjectFactory<?> objectFactory) {
464                synchronized (this.attributes) {
465                        if (!this.attributes.containsKey(name)) {
466                                this.attributes.put(name, objectFactory.getObject());
467                        }
468                        return this.attributes.get(name);
469                }
470        }
471
472        public Object removeAttribute(String name) {
473                synchronized (this.attributes) {
474                        return this.attributes.remove(name);
475                }
476        }
477
478        /**
479         * Return the initial set of URLs as configured by the {@link RestartInitializer}.
480         * @return the initial URLs or {@code null}
481         */
482        public URL[] getInitialUrls() {
483                return this.initialUrls;
484        }
485
486        /**
487         * Initialize and disable restart support.
488         */
489        public static void disable() {
490                initialize(NO_ARGS, false, RestartInitializer.NONE);
491                getInstance().setEnabled(false);
492        }
493
494        /**
495         * Initialize restart support. See
496         * {@link #initialize(String[], boolean, RestartInitializer)} for details.
497         * @param args main application arguments
498         * @see #initialize(String[], boolean, RestartInitializer)
499         */
500        public static void initialize(String[] args) {
501                initialize(args, false, new DefaultRestartInitializer());
502        }
503
504        /**
505         * Initialize restart support. See
506         * {@link #initialize(String[], boolean, RestartInitializer)} for details.
507         * @param args main application arguments
508         * @param initializer the restart initializer
509         * @see #initialize(String[], boolean, RestartInitializer)
510         */
511        public static void initialize(String[] args, RestartInitializer initializer) {
512                initialize(args, false, initializer, true);
513        }
514
515        /**
516         * Initialize restart support. See
517         * {@link #initialize(String[], boolean, RestartInitializer)} for details.
518         * @param args main application arguments
519         * @param forceReferenceCleanup if forcing of soft/weak reference should happen on
520         * @see #initialize(String[], boolean, RestartInitializer)
521         */
522        public static void initialize(String[] args, boolean forceReferenceCleanup) {
523                initialize(args, forceReferenceCleanup, new DefaultRestartInitializer());
524        }
525
526        /**
527         * Initialize restart support. See
528         * {@link #initialize(String[], boolean, RestartInitializer, boolean)} for details.
529         * @param args main application arguments
530         * @param forceReferenceCleanup if forcing of soft/weak reference should happen on
531         * @param initializer the restart initializer
532         * @see #initialize(String[], boolean, RestartInitializer)
533         */
534        public static void initialize(String[] args, boolean forceReferenceCleanup,
535                        RestartInitializer initializer) {
536                initialize(args, forceReferenceCleanup, initializer, true);
537        }
538
539        /**
540         * Initialize restart support for the current application. Called automatically by
541         * {@link RestartApplicationListener} but can also be called directly if main
542         * application arguments are not the same as those passed to the
543         * {@link SpringApplication}.
544         * @param args main application arguments
545         * @param forceReferenceCleanup if forcing of soft/weak reference should happen on
546         * each restart. This will slow down restarts and is intended primarily for testing
547         * @param initializer the restart initializer
548         * @param restartOnInitialize if the restarter should be restarted immediately when
549         * the {@link RestartInitializer} returns non {@code null} results
550         */
551        public static void initialize(String[] args, boolean forceReferenceCleanup,
552                        RestartInitializer initializer, boolean restartOnInitialize) {
553                Restarter localInstance = null;
554                synchronized (INSTANCE_MONITOR) {
555                        if (instance == null) {
556                                localInstance = new Restarter(Thread.currentThread(), args,
557                                                forceReferenceCleanup, initializer);
558                                instance = localInstance;
559                        }
560                }
561                if (localInstance != null) {
562                        localInstance.initialize(restartOnInitialize);
563                }
564        }
565
566        /**
567         * Return the active {@link Restarter} instance. Cannot be called before
568         * {@link #initialize(String[]) initialization}.
569         * @return the restarter
570         */
571        public static Restarter getInstance() {
572                synchronized (INSTANCE_MONITOR) {
573                        Assert.state(instance != null, "Restarter has not been initialized");
574                        return instance;
575                }
576        }
577
578        /**
579         * Set the restarter instance (useful for testing).
580         * @param instance the instance to set
581         */
582        static void setInstance(Restarter instance) {
583                synchronized (INSTANCE_MONITOR) {
584                        Restarter.instance = instance;
585                }
586        }
587
588        /**
589         * Clear the instance. Primarily provided for tests and not usually used in
590         * application code.
591         */
592        public static void clearInstance() {
593                synchronized (INSTANCE_MONITOR) {
594                        instance = null;
595                }
596        }
597
598        /**
599         * Thread that is created early so not to retain the {@link RestartClassLoader}.
600         */
601        private class LeakSafeThread extends Thread {
602
603                private Callable<?> callable;
604
605                private Object result;
606
607                LeakSafeThread() {
608                        setDaemon(false);
609                }
610
611                public void call(Callable<?> callable) {
612                        this.callable = callable;
613                        start();
614                }
615
616                @SuppressWarnings("unchecked")
617                public <V> V callAndWait(Callable<V> callable) {
618                        this.callable = callable;
619                        start();
620                        try {
621                                join();
622                                return (V) this.result;
623                        }
624                        catch (InterruptedException ex) {
625                                Thread.currentThread().interrupt();
626                                throw new IllegalStateException(ex);
627                        }
628                }
629
630                @Override
631                public void run() {
632                        // We are safe to refresh the ActionThread (and indirectly call
633                        // AccessController.getContext()) since our stack doesn't include the
634                        // RestartClassLoader
635                        try {
636                                Restarter.this.leakSafeThreads.put(new LeakSafeThread());
637                                this.result = this.callable.call();
638                        }
639                        catch (Exception ex) {
640                                ex.printStackTrace();
641                                System.exit(1);
642                        }
643                }
644
645        }
646
647        /**
648         * {@link ThreadFactory} that creates a leak safe thread.
649         */
650        private class LeakSafeThreadFactory implements ThreadFactory {
651
652                @Override
653                public Thread newThread(Runnable runnable) {
654                        return getLeakSafeThread().callAndWait(() -> {
655                                Thread thread = new Thread(runnable);
656                                thread.setContextClassLoader(Restarter.this.applicationClassLoader);
657                                return thread;
658                        });
659                }
660
661        }
662
663}