Rework the ThrustCurve motor loading process to be highly parallel. This significant...
[debian/openrocket] / core / src / net / sf / openrocket / startup / ConcurrentLoadingThrustCurveMotorSetDatabase.java
1 package net.sf.openrocket.startup;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.util.List;
7 import java.util.concurrent.ExecutorService;
8 import java.util.concurrent.Executors;
9 import java.util.concurrent.TimeUnit;
10
11 import net.sf.openrocket.database.ThrustCurveMotorSet;
12 import net.sf.openrocket.database.ThrustCurveMotorSetDatabase;
13 import net.sf.openrocket.file.iterator.DirectoryIterator;
14 import net.sf.openrocket.file.iterator.FileIterator;
15 import net.sf.openrocket.file.motor.GeneralMotorLoader;
16 import net.sf.openrocket.file.motor.MotorLoaderHelper;
17 import net.sf.openrocket.gui.util.SimpleFileFilter;
18 import net.sf.openrocket.gui.util.SwingPreferences;
19 import net.sf.openrocket.logging.LogHelper;
20 import net.sf.openrocket.motor.Motor;
21 import net.sf.openrocket.motor.ThrustCurveMotor;
22 import net.sf.openrocket.util.BugException;
23 import net.sf.openrocket.util.Pair;
24
25 /**
26  * Load motors in parallel using a three stage pipeline.
27  * 
28  * Stage 1: single thread managed by the ThrustCurveMotorSetDatabase.  This thread generates
29  *          one object for each thrust curve motor file and puts it in the second stage.
30  *          
31  * Stage 2: multiple threads which process individual files.  Each process takes
32  *          a single motor file and parses out the list of motors it contains.
33  *          The list of motors is queued up for the third stage to process.
34  *          
35  * Stage 3: single thread which processes the list of motors generated in stage 2.
36  *          This thread puts all the motors from the list in the motor set database.
37  *          
38  * It is important that stage 3 be done with a single thread because ThrustCurveMotorSetDatabase
39  * is not thread safe.  Even if synchronization were to be done, it is unlikely that parallelizing
40  * this process would improve anything.
41  * 
42  *
43  */
44 public class ConcurrentLoadingThrustCurveMotorSetDatabase extends ThrustCurveMotorSetDatabase {
45
46         private static final LogHelper log = Application.getLogger();
47         private final String thrustCurveDirectory;
48
49
50         public ConcurrentLoadingThrustCurveMotorSetDatabase(String thrustCurveDirectory) {
51                 // configure ThrustCurveMotorSetDatabase as true so we get our own thread in
52                 // loadMotors.
53                 super(true);
54                 this.thrustCurveDirectory = thrustCurveDirectory;
55         }
56
57         @Override
58         protected void loadMotors() {
59
60                 BookKeeping keeper = new BookKeeping();
61                 keeper.start();
62
63                 try {
64                         keeper.waitForFinish();
65                 }
66                 catch ( InterruptedException iex ) {
67                         throw new BugException(iex);
68                 }
69
70                 keeper = null;
71
72         }
73
74         private void addAll( List<Motor> motors ) {
75                 for (Motor m : motors) {
76                         addMotor( (ThrustCurveMotor) m);
77                 }
78         }
79
80         /**
81          * A class which holds all the threading data.
82          * Implemented as an inner class so we can easily jettison the references when
83          * the processing is terminated.
84          *
85          */
86         private class BookKeeping {
87
88                 /*
89                  * Executor for Stage 3.
90                  */
91                 private final ExecutorService writerThread;
92
93                 /*
94                  * Executor for Stage 2.
95                  */
96                 private final ExecutorService loaderPool;
97
98                 /*
99                  * Runnable used for Stage 1.
100                  */
101                 private final WorkGenerator workGenerator;
102
103                 private long startTime;
104
105                 /*
106                  * Number of thrust curves loaded
107                  */
108                 private int thrustCurveCount = 0;
109
110                 /*
111                  * Number of files processed.
112                  */
113                 private int fileCount = 0;
114
115                 /*
116                  * We have to hold on to the zip file iterator which is used to load
117                  * the system motor files until all processing is done.  This is because
118                  * closing the iterator prematurely causes all the InputStreams opened
119                  * with it to close. 
120                  */
121                 private FileIterator iterator;
122
123                 private BookKeeping() {
124
125                         writerThread = Executors.newSingleThreadExecutor();
126
127                         loaderPool = Executors.newFixedThreadPool(25);
128
129                         workGenerator = new WorkGenerator();
130
131                 }
132
133                 private void start() {
134
135                         startTime = System.currentTimeMillis();
136
137                         log.info("Starting motor loading from " + thrustCurveDirectory + " in background thread.");
138
139                         // Run the work generator - in this thread.
140                         workGenerator.run();
141
142                 }
143
144                 private void waitForFinish() throws InterruptedException {
145                         try {
146                                 loaderPool.shutdown();
147                                 loaderPool.awaitTermination(10, TimeUnit.SECONDS);
148                                 writerThread.shutdown();
149                                 writerThread.awaitTermination(10, TimeUnit.SECONDS);
150                         }
151                         finally {
152                                 iterator.close();
153                         }
154
155                         long endTime = System.currentTimeMillis();
156
157                         int distinctMotorCount = 0;
158                         int distinctThrustCurveCount = 0;
159                         distinctMotorCount = motorSets.size();
160                         for (ThrustCurveMotorSet set : motorSets) {
161                                 distinctThrustCurveCount += set.getMotorCount();
162                         }
163
164                         log.info("Motor loading done, took " + (endTime - startTime) + " ms to load " 
165                                         + fileCount + " files/directories containing " 
166                                         + thrustCurveCount + " thrust curves which contained "
167                                         + distinctMotorCount + " distinct motors with "
168                                         + distinctThrustCurveCount + " distinct thrust curves.");
169
170                 }
171
172
173                 private class WorkGenerator implements Runnable {
174
175                         @Override
176                         public void run() {
177                                 // Start loading
178                                 log.info("Loading motors from " + thrustCurveDirectory);
179
180                                 iterator = DirectoryIterator.findDirectory(thrustCurveDirectory,
181                                                 new SimpleFileFilter("", false, "eng", "rse"));
182
183                                 // Load the packaged thrust curves
184                                 if (iterator == null) {
185                                         throw new IllegalStateException("Thrust curve directory " + thrustCurveDirectory +
186                                                         "not found, distribution built wrong");
187                                 }
188
189                                 while( iterator.hasNext() ) {
190                                         Pair<String,InputStream> f = iterator.next();
191                                         MotorLoader loader = new MotorLoader( f.getV(), f.getU() );
192                                         loaderPool.execute(loader);
193                                         fileCount ++;
194                                 }
195
196                                 // Load the user-defined thrust curves
197                                 for (File file : ((SwingPreferences) Application.getPreferences()).getUserThrustCurveFiles()) {
198                                         log.info("Loading motors from " + file);
199                                         MotorLoader loader = new MotorLoader( file );
200                                         loaderPool.execute(loader);
201                                         fileCount++;
202                                 }
203                         }
204                 }
205
206                 private class MotorLoader implements Runnable {
207
208                         private final InputStream is;
209                         private final String fileName;
210
211                         private final File file;
212
213                         public MotorLoader( File file ) {
214                                 super();
215                                 this.file = file;
216                                 this.is = null;
217                                 this.fileName = null;
218                         }
219
220                         public MotorLoader(InputStream is, String fileName) {
221                                 super();
222                                 this.file = null;
223                                 this.is = is;
224                                 this.fileName = fileName;
225                         }
226
227                         @Override
228                         public void run() {
229                                 log.debug("Loading motor from " + fileName);
230
231                                 try {
232                                         List<Motor> motors;
233                                         if ( file == null ) {
234                                                 motors = MotorLoaderHelper.load(is, fileName);
235                                         } else {
236                                                 motors = MotorLoaderHelper.load(file);
237                                         }
238                                         writerThread.submit( new MotorInserter(motors));
239                                 }
240                                 finally {
241                                         if ( is != null ) {
242                                                 try {
243                                                         is.close();
244                                                 } catch ( IOException iex ) {
245                                                 }
246                                         }
247                                 }
248                         }
249                 }
250
251                 private class MotorInserter implements Runnable {
252
253                         private final List<Motor> motors;
254
255                         MotorInserter( List<Motor> motors ) {
256                                 this.motors = motors;
257                         }
258
259                         @Override
260                         public void run() {
261                                 thrustCurveCount += motors.size();
262                                 ConcurrentLoadingThrustCurveMotorSetDatabase.this.addAll(motors);
263                         }
264
265                 }
266         }
267
268 }