Mercurial > projects > mde
annotate mde/scheduler/Init.d @ 24:32eff0e01c05
Only locally-changed options are stored in user-config now. Log levels revised.
Options sub-classes are handled more generically and can be added without changing the Options class.
Options changed at run-time are tracked, and on exit merged with user options and saved.
Revised log levels as set out in policies.txt and as used in code.
committer: Diggory Hardy <diggory.hardy@gmail.com>
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Thu, 27 Mar 2008 16:15:21 +0000 |
parents | a60cbb7359dd |
children | 2c28ee04a4ed |
rev | line source |
---|---|
20 | 1 /* LICENSE BLOCK |
2 Part of mde: a Modular D game-oriented Engine | |
3 Copyright © 2007-2008 Diggory Hardy | |
4 | |
5 This program is free software; you can redistribute it and/or modify it under the terms of | |
6 the GNU General Public License, version 2, as published by the Free Software Foundation. | |
7 | |
8 This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; | |
9 without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
10 See the GNU General Public License for more details. | |
11 | |
12 You should have received a copy of the GNU General Public License along | |
13 with this program; if not, write to the Free Software Foundation, Inc., | |
14 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ | |
15 | |
16 /************************************************************************************************** | |
17 * Initialisation setup and exit cleanup module. | |
18 * | |
19 * This module provides an infrastructure for handling much of the initialisation and | |
20 * deinitialisation of the program. It does not, however, provide much of the (de)initialisation | |
21 * code; with the exception of that for the logger. | |
22 *************************************************************************************************/ | |
23 module mde.scheduler.Init; | |
24 | |
25 import mde.scheduler.InitStage; | |
26 import mde.scheduler.exception; | |
27 | |
28 import mde.options; | |
29 import paths = mde.resource.paths; | |
30 import mde.exception; | |
31 | |
32 // tango imports | |
33 import tango.core.Thread; | |
34 import tango.core.Exception; | |
35 import tango.stdc.stringz : fromStringz; | |
36 import tango.util.log.Log : Log, Logger; | |
37 import tango.util.log.ConsoleAppender : ConsoleAppender; | |
38 | |
39 version = SwitchAppender; | |
40 version (SwitchAppender) { // My own variation, currently just a test | |
41 import tango.util.log.SwitchingFileAppender : SwitchingFileAppender; | |
42 } else { | |
43 import tango.util.log.RollingFileAppender : RollingFileAppender; | |
44 } | |
45 | |
46 /** | |
47 * Static CTOR | |
48 * | |
49 * This should handle a minimal amount of functionality where useful. For instance, configuring the | |
50 * logger here and not in Init allows unittests to use the logger. | |
51 */ | |
52 static this() | |
53 { | |
54 // Find/create paths: | |
55 try { | |
56 paths.resolvePaths(); | |
57 } catch (Exception e) { | |
58 throw new InitException ("Resolving paths failed: " ~ e.msg); | |
59 } | |
60 | |
61 // Set up the logger: | |
62 { | |
63 // Where logging is done to is determined at compile-time, currently just via static ifs. | |
64 Logger root = Log.getRootLogger(); | |
65 | |
66 static if (true ) { // Log to the console | |
67 root.addAppender(new ConsoleAppender); | |
68 } | |
69 static if (true ) { // Log to files | |
70 version (SwitchAppender) { | |
71 root.addAppender (new SwitchingFileAppender (paths.logDir~"/log-.txt", 5)); | |
72 } else { | |
73 // Use 2 log files with a maximum size of 1 MB: | |
74 root.addAppender (new RollingFileAppender (paths.logDir~"/log-.txt", 2, 1024*1024)); | |
75 } | |
76 } | |
77 | |
78 // Set the level here, but set it again once options have been loaded: | |
79 debug root.setLevel(root.Level.Trace); | |
80 else root.setLevel(root.Level.Info); | |
81 } | |
82 } | |
83 static ~this() | |
84 { | |
85 } | |
86 | |
87 /** | |
88 * Init class | |
89 * | |
90 * A scope class created at beginning of the program and destroyed at the end; thus the CTOR | |
91 * handles program initialisation and the DTOR handles program cleanup. | |
92 */ | |
93 scope class Init | |
94 { | |
95 private static Logger logger; | |
96 static this() { | |
97 logger = Log.getLogger ("mde.scheduler.Init.Init"); | |
98 } | |
99 | |
100 /** CTOR − initialisation | |
101 * | |
102 * Runs general initialisation code, in a threaded manner where this isn't difficult. | |
103 * | |
104 * If any init fails, it must run necessary cleanup first since the DTOR cannot(?) be run. */ | |
105 /* In a single-threaded function this could be done with: | |
106 * scope(failure) cleanup; | |
107 * This won't work with a threaded init function since any threads completing succesfully will | |
108 * not clean-up, and a fixed list of clean-up functions cannot be used since clean-up functions | |
109 * must not run where the initialisation functions have failed. | |
110 * Hence a list of clean-up functions is built similarly to scope(failure) --- see addCleanupFct. | |
111 */ | |
112 this() | |
113 { | |
24
32eff0e01c05
Only locally-changed options are stored in user-config now. Log levels revised.
Diggory Hardy <diggory.hardy@gmail.com>
parents:
21
diff
changeset
|
114 debug logger.trace ("Init: starting"); |
20 | 115 |
116 //BEGIN Pre-init (stage init0) | |
117 /* Load options now. Don't load in a thread since: | |
118 * Loading should be fast | |
119 * Most work is probably disk access | |
120 * It's a really good idea to let the options apply to all other loading. */ | |
121 try { | |
122 Options.load(); | |
123 } catch (optionsLoadException e) { | |
124 throw new InitException ("Loading options failed; message: " ~ e.msg); | |
125 } | |
126 | |
127 // Now re-set the logging level: | |
24
32eff0e01c05
Only locally-changed options are stored in user-config now. Log levels revised.
Diggory Hardy <diggory.hardy@gmail.com>
parents:
21
diff
changeset
|
128 Log.getRootLogger.setLevel (cast(Log.Level) miscOpts.logLevel, true); // set the stored log level |
20 | 129 //END Pre-init |
130 | |
131 | |
132 //BEGIN Init (stages init2, init4) | |
133 /* Call init functions. | |
134 * | |
135 * Current method is to try using threads, and on failure assume no threads were actually | |
136 * created and run functions in a non-threaded manner. */ | |
137 | |
21
a60cbb7359dd
Window settings now come from options, and may use OpenGL (enabled/disabled at compile time).
Diggory Hardy <diggory.hardy@gmail.com>
parents:
20
diff
changeset
|
138 // init2 |
a60cbb7359dd
Window settings now come from options, and may use OpenGL (enabled/disabled at compile time).
Diggory Hardy <diggory.hardy@gmail.com>
parents:
20
diff
changeset
|
139 cleanupStages ~= &cleanup2; // add appropriate cleanup stage |
a60cbb7359dd
Window settings now come from options, and may use OpenGL (enabled/disabled at compile time).
Diggory Hardy <diggory.hardy@gmail.com>
parents:
20
diff
changeset
|
140 try { |
24
32eff0e01c05
Only locally-changed options are stored in user-config now. Log levels revised.
Diggory Hardy <diggory.hardy@gmail.com>
parents:
21
diff
changeset
|
141 debug logger.trace ("Init: init2"); |
20 | 142 if (runStageThreaded (init2)) runStageForward (init2); |
143 } | |
144 catch (InitStageException) { // This init stage failed. | |
24
32eff0e01c05
Only locally-changed options are stored in user-config now. Log levels revised.
Diggory Hardy <diggory.hardy@gmail.com>
parents:
21
diff
changeset
|
145 debug logger.trace ("Init: init2 failed"); |
20 | 146 runCleanupStages(); |
147 throw new InitException ("Initialisation failed during stage init2"); | |
148 } | |
149 | |
21
a60cbb7359dd
Window settings now come from options, and may use OpenGL (enabled/disabled at compile time).
Diggory Hardy <diggory.hardy@gmail.com>
parents:
20
diff
changeset
|
150 // init4 |
a60cbb7359dd
Window settings now come from options, and may use OpenGL (enabled/disabled at compile time).
Diggory Hardy <diggory.hardy@gmail.com>
parents:
20
diff
changeset
|
151 cleanupStages ~= &cleanup4; // add appropriate cleanup stage |
a60cbb7359dd
Window settings now come from options, and may use OpenGL (enabled/disabled at compile time).
Diggory Hardy <diggory.hardy@gmail.com>
parents:
20
diff
changeset
|
152 try { |
24
32eff0e01c05
Only locally-changed options are stored in user-config now. Log levels revised.
Diggory Hardy <diggory.hardy@gmail.com>
parents:
21
diff
changeset
|
153 debug logger.trace ("Init: init4"); |
20 | 154 if (runStageThreaded (init4)) runStageForward (init4); |
155 } | |
156 catch (InitStageException) { // This init stage failed. | |
24
32eff0e01c05
Only locally-changed options are stored in user-config now. Log levels revised.
Diggory Hardy <diggory.hardy@gmail.com>
parents:
21
diff
changeset
|
157 debug logger.trace ("Init: init4 failed"); |
20 | 158 runCleanupStages(); |
159 throw new InitException ("Initialisation failed during stage init4"); | |
160 } | |
161 //END Init | |
162 | |
24
32eff0e01c05
Only locally-changed options are stored in user-config now. Log levels revised.
Diggory Hardy <diggory.hardy@gmail.com>
parents:
21
diff
changeset
|
163 debug logger.trace ("Init: done"); |
20 | 164 } |
165 | |
166 /** DTOR - runs cleanup functions. */ | |
167 ~this() | |
168 { | |
24
32eff0e01c05
Only locally-changed options are stored in user-config now. Log levels revised.
Diggory Hardy <diggory.hardy@gmail.com>
parents:
21
diff
changeset
|
169 debug logger.trace ("Cleanup: starting"); |
20 | 170 |
171 // cleanup1: | |
172 Options.save(); // save options... do so here for now | |
173 | |
174 // cleanup2, 4: | |
175 runCleanupStages(); | |
176 | |
24
32eff0e01c05
Only locally-changed options are stored in user-config now. Log levels revised.
Diggory Hardy <diggory.hardy@gmail.com>
parents:
21
diff
changeset
|
177 debug logger.trace ("Cleanup: done"); |
20 | 178 } |
179 | |
180 | |
181 //BEGIN runStage... | |
182 private static { | |
183 /* The following three functions, runStage*, each run all functions in a stage in some order, | |
184 * catching any exceptions thrown by the functions (although this isn't guaranteed for threads), | |
21
a60cbb7359dd
Window settings now come from options, and may use OpenGL (enabled/disabled at compile time).
Diggory Hardy <diggory.hardy@gmail.com>
parents:
20
diff
changeset
|
185 * and throw an InitStageException on initFailure. */ |
20 | 186 |
187 const UFE = "Unhandled exception from Init function:"; | |
188 /* Runs all functions consecutively, first-to-last. | |
189 * If any function fails, halts immediately. */ | |
190 void runStageForward (InitStage s) { | |
191 foreach (func; s.funcs) { | |
21
a60cbb7359dd
Window settings now come from options, and may use OpenGL (enabled/disabled at compile time).
Diggory Hardy <diggory.hardy@gmail.com>
parents:
20
diff
changeset
|
192 if (initFailure) break; |
20 | 193 try { |
194 func(); | |
195 } catch (Exception e) { | |
196 logger.fatal (UFE); | |
197 logger.fatal (e.msg); | |
198 | |
21
a60cbb7359dd
Window settings now come from options, and may use OpenGL (enabled/disabled at compile time).
Diggory Hardy <diggory.hardy@gmail.com>
parents:
20
diff
changeset
|
199 setInitFailure(); |
20 | 200 } |
201 } | |
21
a60cbb7359dd
Window settings now come from options, and may use OpenGL (enabled/disabled at compile time).
Diggory Hardy <diggory.hardy@gmail.com>
parents:
20
diff
changeset
|
202 |
a60cbb7359dd
Window settings now come from options, and may use OpenGL (enabled/disabled at compile time).
Diggory Hardy <diggory.hardy@gmail.com>
parents:
20
diff
changeset
|
203 if (initFailure) throw new InitStageException; // Problem running; abort and cleanup from here. |
20 | 204 } |
205 /* Runs all functions consecutively, last-to-first. | |
206 * If any function fails, continue until all have been run. */ | |
207 void runStageReverse (InitStage s) { | |
208 foreach_reverse (func; s.funcs) { | |
209 try { | |
210 func(); | |
211 } catch (Exception e) { | |
212 logger.fatal (UFE); | |
213 logger.fatal (e.msg); | |
214 | |
21
a60cbb7359dd
Window settings now come from options, and may use OpenGL (enabled/disabled at compile time).
Diggory Hardy <diggory.hardy@gmail.com>
parents:
20
diff
changeset
|
215 setInitFailure(); |
20 | 216 } |
217 } | |
21
a60cbb7359dd
Window settings now come from options, and may use OpenGL (enabled/disabled at compile time).
Diggory Hardy <diggory.hardy@gmail.com>
parents:
20
diff
changeset
|
218 if (initFailure) throw new InitStageException; // Problem running; abort and cleanup from here. |
20 | 219 } |
220 /* Tries running functions in a threaded way. Returns false if successful, true if not but | |
221 * functions should be run without threads. */ | |
222 bool runStageThreaded (InitStage s) { | |
24
32eff0e01c05
Only locally-changed options are stored in user-config now. Log levels revised.
Diggory Hardy <diggory.hardy@gmail.com>
parents:
21
diff
changeset
|
223 if (!miscOpts.useThreads) return true; // Use unthreaded route instead |
20 | 224 |
225 ThreadGroup tg; | |
226 try { // creating/starting threads could fail | |
227 tg = new ThreadGroup; | |
228 foreach (func; s.funcs) tg.create(func); // Start all threads | |
229 } catch (ThreadException e) { // Problem with threading; try without threads | |
24
32eff0e01c05
Only locally-changed options are stored in user-config now. Log levels revised.
Diggory Hardy <diggory.hardy@gmail.com>
parents:
21
diff
changeset
|
230 logger.error ("Caught ThreadException while trying to create threads:"); |
32eff0e01c05
Only locally-changed options are stored in user-config now. Log levels revised.
Diggory Hardy <diggory.hardy@gmail.com>
parents:
21
diff
changeset
|
231 logger.error (e.msg); |
32eff0e01c05
Only locally-changed options are stored in user-config now. Log levels revised.
Diggory Hardy <diggory.hardy@gmail.com>
parents:
21
diff
changeset
|
232 logger.info ("Will disable threads and continue."); |
20 | 233 |
24
32eff0e01c05
Only locally-changed options are stored in user-config now. Log levels revised.
Diggory Hardy <diggory.hardy@gmail.com>
parents:
21
diff
changeset
|
234 Options.setBool("misc", "useThreads", false); // Disable threads entirely |
20 | 235 return true; // Try again without threads |
236 } | |
237 | |
238 /* Wait for all threads to complete. | |
239 * | |
240 * If something went wrong, we still need to do this before cleaning up. | |
241 */ | |
242 foreach (t; tg) { | |
243 try { | |
244 t.join (true); | |
245 } catch (Exception e) { | |
246 // Relying on catching exceptions thrown by other threads is a bad idea. | |
247 // Hence all threads should catch their own exceptions and return safely. | |
248 | |
249 logger.fatal ("Unhandled exception from Init function:"); | |
250 logger.fatal (e.msg); | |
251 | |
21
a60cbb7359dd
Window settings now come from options, and may use OpenGL (enabled/disabled at compile time).
Diggory Hardy <diggory.hardy@gmail.com>
parents:
20
diff
changeset
|
252 setInitFailure (); // abort (but join other threads first) |
20 | 253 } |
254 } | |
21
a60cbb7359dd
Window settings now come from options, and may use OpenGL (enabled/disabled at compile time).
Diggory Hardy <diggory.hardy@gmail.com>
parents:
20
diff
changeset
|
255 |
a60cbb7359dd
Window settings now come from options, and may use OpenGL (enabled/disabled at compile time).
Diggory Hardy <diggory.hardy@gmail.com>
parents:
20
diff
changeset
|
256 if (initFailure) throw new InitStageException; // Problem running; abort and cleanup from here. |
20 | 257 return false; // Done successfully |
258 } | |
259 } | |
260 //END runStage... | |
261 | |
262 private { | |
263 InitStage*[] cleanupStages; // All cleanup stages needing to be run later. | |
264 void runCleanupStages () { | |
265 foreach_reverse (s; cleanupStages) { | |
266 try { | |
267 runStageReverse (*s); | |
268 } | |
269 catch (InitStageException) { | |
270 // We're cleaning up anyway, just report and continue | |
271 logger.error ("Cleanup function failed! Continuing..."); | |
272 } | |
273 } | |
274 cleanupStages = []; // All have been run, don't want them getting run again | |
275 } | |
276 } | |
277 | |
278 debug (mdeUnitTest) unittest { | |
279 /* Fake init and cleanup. Use unittest-specific init and cleanup InitStages to avoid | |
280 * messing other init/cleanup up. */ | |
281 static InitStage initUT, cleanupUT; | |
282 | |
283 static bool initialised = false; | |
284 static void cleanupFunc1 () { | |
285 initialised = false; | |
286 } | |
287 static void cleanupFunc2 () { | |
288 assert (initialised == true); | |
289 } | |
290 | |
291 static void initFunc () { | |
292 initialised = true; | |
293 cleanupUT.addFunc (&cleanupFunc1); | |
294 cleanupUT.addFunc (&cleanupFunc2); | |
295 } | |
296 | |
297 initUT.addFunc (&initFunc); | |
298 | |
299 runStageForward (initUT); | |
300 assert (initialised); | |
301 | |
302 runStageReverse (cleanupUT); | |
303 assert (!initialised); | |
304 | |
305 logger.info ("Unittest complete."); | |
306 } | |
307 } |