001/**
002 * Copyright 2010-2014 The Kuali Foundation
003 *
004 * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
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 */
016package org.kuali.common.util.execute.impl;
017
018import static com.google.common.base.Optional.absent;
019import static com.google.common.base.Stopwatch.createStarted;
020import static com.google.common.collect.Lists.newArrayList;
021import static java.lang.Math.ceil;
022import static java.lang.Math.max;
023import static org.kuali.common.util.FormatUtils.getTime;
024import static org.kuali.common.util.base.Precondition.checkNotNull;
025import static org.kuali.common.util.log.Loggers.newLogger;
026
027import java.lang.Thread.UncaughtExceptionHandler;
028import java.util.List;
029
030import org.kuali.common.util.base.Threads;
031import org.kuali.common.util.execute.Executable;
032import org.slf4j.Logger;
033
034import com.google.common.base.Optional;
035import com.google.common.base.Stopwatch;
036import com.google.common.collect.ImmutableList;
037import com.google.common.collect.Lists;
038
039/**
040 * Create a new thread for each executable in the list and run them all concurrently.
041 */
042public final class ConcurrentExecutables implements Executable, UncaughtExceptionHandler {
043
044        private static final Logger logger = newLogger();
045
046        private final ImmutableList<Executable> executables;
047        private final boolean skip;
048        private final boolean timed;
049        private final Optional<Integer> maxThreads;
050
051        // If any thread throws an exception, this gets filled in
052        private Optional<IllegalStateException> uncaughtException = absent();
053
054        public static void execute(Executable... executables) {
055                create(executables).execute();
056        }
057
058        public static void execute(List<Executable> executables) {
059                create(executables).execute();
060        }
061
062        public static void executeConcurrently(List<Executable> executables, int maxThreads) {
063                builder(executables).withMaxThreads(maxThreads).build().execute();
064        }
065
066        public static ConcurrentExecutables create(Executable... executables) {
067                return builder(executables).build();
068        }
069
070        public static ConcurrentExecutables create(List<Executable> executables) {
071                return builder(executables).build();
072        }
073
074        public static Builder builder(Executable... executables) {
075                return new Builder(executables);
076        }
077
078        public static Builder builder(List<Executable> executables) {
079                return new Builder(executables);
080        }
081
082        public static class Builder implements org.apache.commons.lang3.builder.Builder<ConcurrentExecutables> {
083
084                // Required
085                private final List<Executable> executables;
086
087                // Optional
088                private boolean skip = false;
089                private boolean timed = false;
090                private Optional<Integer> maxThreads = absent();
091
092                public Builder(Executable... executables) {
093                        this(ImmutableList.copyOf(executables));
094                }
095
096                public Builder(List<Executable> executables) {
097                        this.executables = ImmutableList.copyOf(executables);
098                }
099
100                public Builder timed(boolean timed) {
101                        this.timed = timed;
102                        return this;
103                }
104
105                public Builder skip(boolean skip) {
106                        this.skip = skip;
107                        return this;
108                }
109
110                public Builder withMaxThreads(int maxThreads) {
111                        this.maxThreads = Optional.of(maxThreads);
112                        return this;
113                }
114
115                @Override
116                public ConcurrentExecutables build() {
117                        ConcurrentExecutables instance = new ConcurrentExecutables(this);
118                        validate(instance);
119                        return instance;
120                }
121
122                private static void validate(ConcurrentExecutables instance) {
123                        checkNotNull(instance.executables, "executables");
124                        checkNotNull(instance.uncaughtException, "uncaughtException");
125                }
126        }
127
128        private ConcurrentExecutables(Builder builder) {
129                this.executables = ImmutableList.copyOf(builder.executables);
130                this.skip = builder.skip;
131                this.timed = builder.timed;
132                this.maxThreads = builder.maxThreads;
133        }
134
135        @Override
136        public void execute() {
137                if (skip) {
138                        logger.info("Skipping execution of {} executables", executables.size());
139                        return;
140                }
141                List<Thread> threads = getThreads(executables, maxThreads);
142                Stopwatch stopwatch = createStarted();
143                Threads.start(threads);
144                Threads.join(threads);
145                if (uncaughtException.isPresent()) {
146                        throw uncaughtException.get();
147                }
148                if (timed) {
149                        logger.info("------------------------------------------------------------------------");
150                        logger.info("Total Time: {} (Wall Clock)", getTime(stopwatch));
151                        logger.info("------------------------------------------------------------------------");
152                }
153        }
154
155        protected List<Thread> getThreads(List<Executable> executables, Optional<Integer> maxThreads) {
156                int max = maxThreads.isPresent() ? maxThreads.get() : executables.size();
157                int size = (int) max(ceil(executables.size() / (max * 1D)), 1);
158                List<List<Executable>> partitions = Lists.partition(executables, size);
159                List<Thread> threads = newArrayList();
160                for (List<Executable> partition : partitions) {
161                        Runnable runnable = new ExecutablesRunner(partition);
162                        Thread thread = new Thread(runnable, "Executable");
163                        thread.setUncaughtExceptionHandler(this);
164                        threads.add(thread);
165                }
166                return threads;
167        }
168
169        @Override
170        public synchronized void uncaughtException(Thread thread, Throwable uncaughtException) {
171                // Only report back on the first uncaught exception reported by any thread
172                // Any exceptions after the first one get ignored
173                if (!this.uncaughtException.isPresent()) {
174                        String context = "Exception in thread [" + thread.getId() + ":" + thread.getName() + "]";
175                        this.uncaughtException = Optional.of(new IllegalStateException(context, uncaughtException));
176                }
177        }
178
179        public ImmutableList<Executable> getExecutables() {
180                return executables;
181        }
182
183        public boolean isSkip() {
184                return skip;
185        }
186
187        public boolean isTimed() {
188                return timed;
189        }
190
191        public Optional<IllegalStateException> getUncaughtException() {
192                return uncaughtException;
193        }
194
195}