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}