source: osm/applications/editors/josm/plugins/imagerycache/src/org/mapdb/DBMaker.java@ 29484

Last change on this file since 29484 was 29484, checked in by akks, 11 years ago

JOSM/ImageryCache: updated MapDB (no more deadlocks, Java 1.6 compatible), less crashes, multiple-JOSM support

File size: 20.1 KB
Line 
1/*
2 * Copyright (c) 2012 Jan Kotek
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.mapdb;
18
19import org.mapdb.EngineWrapper.ByteTransformEngine;
20import org.mapdb.EngineWrapper.ReadOnlyEngine;
21
22import java.io.File;
23import java.io.IOError;
24import java.io.IOException;
25import java.io.UnsupportedEncodingException;
26import java.util.NavigableSet;
27import java.util.Set;
28
29/**
30 * A builder class for creating and opening a database.
31 *
32 * @author Jan Kotek
33 */
34public class DBMaker {
35
36 protected static final byte CACHE_DISABLE = 0;
37 protected static final byte CACHE_FIXED_HASH_TABLE = 1;
38 protected static final byte CACHE_HARD_REF = 2;
39 protected static final byte CACHE_WEAK_REF = 3;
40 protected static final byte CACHE_SOFT_REF = 4;
41 protected static final byte CACHE_LRU = 5;
42
43 protected byte _cache = CACHE_FIXED_HASH_TABLE;
44 protected int _cacheSize = 1024*32;
45
46 /** file to open, if null opens in memory store */
47 protected File _file;
48
49 protected boolean _journalEnabled = true;
50
51 protected boolean _asyncWriteEnabled = true;
52 protected int _asyncFlushDelay = 100;
53
54 protected boolean _deleteFilesAfterClose = false;
55 protected boolean _readOnly = false;
56 protected boolean _closeOnJvmShutdown = false;
57
58 protected boolean _compressionEnabled = false;
59
60 protected byte[] _xteaEncryptionKey = null;
61
62 protected int _freeSpaceReclaimQ = 5;
63
64 protected boolean _checksumEnabled = false;
65
66 protected boolean _ifInMemoryUseDirectBuffer = false;
67
68 protected boolean _failOnWrongHeader = false;
69
70 protected boolean _RAF = false;
71 protected boolean _powerSavingMode = false;
72 protected boolean _appendStorage;
73
74 /** use static factory methods, or make subclass */
75 protected DBMaker(){}
76
77 /** Creates new in-memory database. Changes are lost after JVM exits.
78 * <p/>
79 * This will use HEAP memory so Garbage Collector is affected.
80 */
81 public static DBMaker newMemoryDB(){
82 DBMaker m = new DBMaker();
83 m._file = null;
84 return m;
85 }
86
87 /** Creates new in-memory database. Changes are lost after JVM exits.
88 * <p/>
89 * This will use DirectByteBuffer outside of HEAP, so Garbage Collector is not affected
90 *
91 */
92 public static DBMaker newDirectMemoryDB(){
93 DBMaker m = new DBMaker();
94 m._file = null;
95 m._ifInMemoryUseDirectBuffer = true;
96 return m;
97 }
98
99
100 /**
101 * Creates or open append-only database stored in file.
102 * This database uses format otherthan usual file db
103 *
104 * @param file
105 * @return maker
106 */
107 public static DBMaker newAppendFileDB(File file) {
108 DBMaker m = new DBMaker();
109 m._file = file;
110 m._appendStorage = true;
111 return m;
112 }
113
114
115
116 /**
117 * Create new BTreeMap backed by temporary file storage.
118 * This is quick way to create 'throw away' collection.
119 *
120 * <p>Storage is created in temp folder and deleted on JVM shutdown
121 */
122 public static <K,V> BTreeMap<K,V> newTempTreeMap(){
123 return newTempFileDB()
124 .deleteFilesAfterClose()
125 .closeOnJvmShutdown()
126 .writeAheadLogDisable()
127 .make()
128 .getTreeMap("temp");
129 }
130
131 /**
132 * Create new HTreeMap backed by temporary file storage.
133 * This is quick way to create 'throw away' collection.
134 *
135 * <p>Storage is created in temp folder and deleted on JVM shutdown
136 */
137 public static <K,V> HTreeMap<K,V> newTempHashMap(){
138 return newTempFileDB()
139 .deleteFilesAfterClose()
140 .closeOnJvmShutdown()
141 .writeAheadLogDisable()
142 .make()
143 .getHashMap("temp");
144 }
145
146 /**
147 * Create new TreeSet backed by temporary file storage.
148 * This is quick way to create 'throw away' collection.
149 *
150 * <p>Storage is created in temp folder and deleted on JVM shutdown
151 */
152 public static <K> NavigableSet<K> newTempTreeSet(){
153 return newTempFileDB()
154 .deleteFilesAfterClose()
155 .closeOnJvmShutdown()
156 .writeAheadLogDisable()
157 .make()
158 .getTreeSet("temp");
159 }
160
161 /**
162 * Create new HashSet backed by temporary file storage.
163 * This is quick way to create 'throw away' collection.
164 * <p>
165 * Storage is created in temp folder and deleted on JVM shutdown
166 */
167 public static <K> Set<K> newTempHashSet(){
168 return newTempFileDB()
169 .deleteFilesAfterClose()
170 .closeOnJvmShutdown()
171 .writeAheadLogDisable()
172 .make()
173 .getHashSet("temp");
174 }
175
176 /**
177 * Creates new database in temporary folder.
178 *
179 * @return
180 */
181 public static DBMaker newTempFileDB() {
182 try {
183 return newFileDB(File.createTempFile("mapdb-temp","db"));
184 } catch (IOException e) {
185 throw new IOError(e);
186 }
187 }
188
189
190 /** Creates or open database stored in file. */
191 public static DBMaker newFileDB(File file){
192 DBMaker m = new DBMaker();
193 m._file = file;
194 return m;
195 }
196
197
198
199 /**
200 * Transaction journal is enabled by default
201 * You must call <b>DB.commit()</b> to save your changes.
202 * It is possible to disable transaction journal for better write performance
203 * In this case all integrity checks are sacrificed for faster speed.
204 * <p/>
205 * If transaction journal is disabled, all changes are written DIRECTLY into store.
206 * You must call DB.close() method before exit,
207 * otherwise your store <b>WILL BE CORRUPTED</b>
208 *
209 *
210 * @return this builder
211 */
212 public DBMaker writeAheadLogDisable(){
213 this._journalEnabled = false;
214 return this;
215 }
216
217 /**
218 * Instance cache is enabled by default.
219 * This greatly decreases serialization overhead and improves performance.
220 * Call this method to disable instance cache, so an object will always be deserialized.
221 * <p/>
222 * This may workaround some problems
223 *
224 * @return this builder
225 */
226 public DBMaker cacheDisable(){
227 this._cache = CACHE_DISABLE;
228 return this;
229 }
230
231 /**
232 * Enables unbounded hard reference cache.
233 * This cache is good if you have lot of available memory.
234 * <p/>
235 * All fetched records are added to HashMap and stored with hard reference.
236 * To prevent OutOfMemoryExceptions JDBM monitors free memory,
237 * if it is bellow 25% cache is cleared.
238 *
239 * @return this builder
240 */
241 public DBMaker cacheHardRefEnable(){
242 this._cache = CACHE_HARD_REF;
243 return this;
244 }
245
246
247 /**
248 * Enables unbounded cache which uses <code>WeakReference</code>.
249 * Items are removed from cache by Garbage Collector
250 *
251 * @return this builder
252 */
253 public DBMaker cacheWeakRefEnable(){
254 this._cache = CACHE_WEAK_REF;
255 return this;
256 }
257
258 /**
259 * Enables unbounded cache which uses <code>SoftReference</code>.
260 * Items are removed from cache by Garbage Collector
261 *
262 * @return this builder
263 */
264 public DBMaker cacheSoftRefEnable(){
265 this._cache = CACHE_SOFT_REF;
266 return this;
267 }
268
269 /**
270 * Enables Least Recently Used cache. It is fixed size cache and it removes less used items to make space.
271 *
272 * @return this builder
273 */
274 public DBMaker cacheLRUEnable(){
275 this._cache = CACHE_LRU;
276 return this;
277 }
278 /**
279 * Enables compatibility storage mode for 32bit JVMs.
280 * <p/>
281 * By default MapDB uses memory mapped files. However 32bit JVM can only address 2GB of memory.
282 * Also some older JVMs do not handle large memory mapped files well.
283 * We can use {@code RandomAccessFile} which it is slower, but safer and more compatible.
284 * Use this if you are experiencing <b>java.lang.OutOfMemoryError: Map failed</b> exceptions
285 */
286 public DBMaker randomAccessFileEnable() {
287 this._RAF = true;
288 return this;
289 }
290
291
292 /**
293 * Check current JVM for known problems. If JVM does not handle large memory files well, this option
294 * disables memory mapped files, and use safer and slower {@code RandomAccessFile} instead.
295 */
296 public DBMaker randomAccessFileEnableIfNeeded() {
297 this._RAF = !Utils.JVMSupportsLargeMappedFiles();
298 return this;
299 }
300
301 /**
302 * Set cache size. Interpretations depends on cache type.
303 * For fixed size caches (such as FixedHashTable cache) it is maximal number of items in cache.
304 * <p/>
305 * For unbounded caches (such as HardRef cache) it is initial capacity of underlying table (HashMap).
306 * <p/>
307 * Default cache size is 32768.
308 *
309 * @param cacheSize new cache size
310 * @return this builder
311 */
312 public DBMaker cacheSize(int cacheSize){
313 this._cacheSize = cacheSize;
314 return this;
315 }
316
317
318 /**
319 * By default all modifications are queued and written into disk on Background Writer Thread.
320 * So all modifications are performed in asynchronous mode and do not block.
321 * <p/>
322 * It is possible to disable Background Writer Thread, but this greatly hurts concurrency.
323 * Without async writes, all threads blocks until all previous writes are not finished (single big lock).
324 *
325 * <p/>
326 * This may workaround some problems
327 *
328 * @return this builder
329 */
330 public DBMaker asyncWriteDisable(){
331 this._asyncWriteEnabled = false;
332 return this;
333 }
334
335 /**
336 * //TODO put this nice comment somewhere
337 * By default all objects are serialized in Background Writer Thread.
338 * <p/>
339 * This may improve performance. For example with single thread access, Async Serialization offloads
340 * lot of work to second core. Or when multiple values are added into single tree node,
341 * node has to be serialized only once. Without Async Serialization node is serialized each time
342 * node is updated.
343 * <p/>
344 * On other side Async Serialization moves all serialization into single thread. This
345 * hurts performance with many concurrent-independent updates.
346 * <p/>
347 * Async Serialization may also produce some unexpected results when your data classes are not
348 * immutable. Consider example bellow. If Async Serialization is disabled, it always prints 'Peter'.
349 * If it is enabled (by default) it creates race condition and randomly prints 'Peter' or 'Jack',
350 * <pre>
351 * Person person = new Person();
352 * person.setName("Peter");
353 * map.put(id, person)
354 * person.setName("Jack");
355 * //long pause
356 * println(map.get(id).getName());
357 * </pre>
358 *
359 * <p/>
360 * This may also workaround some problems
361 *
362 * @return this builder
363 */
364
365
366 /**
367 * Set flush iterval for write cache, by default is 0
368 * <p/>
369 * When BTreeMap is constructed from ordered set, tree node size is increasing linearly with each
370 * item added. Each time new key is added to tree node, its size changes and
371 * storage needs to find new place. So constructing BTreeMap from ordered set leads to large
372 * store fragmentation.
373 * <p/>
374 * Setting flush interval is workaround as BTreeMap node is always updated in memory (write cache)
375 * and only final version of node is stored on disk.
376 *
377 *
378 * @param delay flush write cache every N miliseconds
379 * @return this builder
380 */
381 public DBMaker asyncFlushDelay(int delay){
382 _asyncFlushDelay = delay;
383 return this;
384 }
385
386
387 /**
388 * Try to delete files after DB is closed.
389 * File deletion may silently fail, especially on Windows where buffer needs to be unmapped file delete.
390 *
391 * @return this builder
392 */
393 public DBMaker deleteFilesAfterClose(){
394 this._deleteFilesAfterClose = true;
395 return this;
396 }
397
398 /**
399 * Adds JVM shutdown hook and closes DB just before JVM;
400 *
401 * @return this builder
402 */
403 public DBMaker closeOnJvmShutdown(){
404 this._closeOnJvmShutdown = true;
405 return this;
406 }
407
408 /**
409 * Enables record compression.
410 * <p/>
411 * Make sure you enable this every time you reopen store, otherwise record de-serialization fails unpredictably.
412 *
413 * @return this builder
414 */
415 public DBMaker compressionEnable(){
416 this._compressionEnabled = true;
417 return this;
418 }
419
420
421 /**
422 * Encrypt storage using XTEA algorithm.
423 * <p/>
424 * XTEA is sound encryption algorithm. However implementation in JDBM was not peer-reviewed.
425 * JDBM only encrypts records data, so attacker may see number of records and their sizes.
426 * <p/>
427 * Make sure you enable this every time you reopen store, otherwise record de-serialization fails unpredictably.
428 *
429 * @param password for encryption
430 * @return this builder
431 */
432 public DBMaker encryptionEnable(String password){
433 try {
434 return encryptionEnable(password.getBytes(Utils.UTF8));
435 } catch (UnsupportedEncodingException e) {
436 throw new RuntimeException(e);
437 }
438 }
439
440
441
442 /**
443 * Encrypt storage using XTEA algorithm.
444 * <p/>
445 * XTEA is sound encryption algorithm. However implementation in JDBM was not peer-reviewed.
446 * JDBM only encrypts records data, so attacker may see number of records and their sizes.
447 * <p/>
448 * Make sure you enable this every time you reopen store, otherwise record de-serialization fails unpredictably.
449 *
450 * @param password for encryption
451 * @return this builder
452 */
453 public DBMaker encryptionEnable(byte[] password){
454 _xteaEncryptionKey = password;
455 return this;
456 }
457
458
459 /**
460 * Adds CRC32 checksum at end of each record to check data integrity.
461 * It throws 'IOException("CRC32 does not match, data broken")' on de-serialization if data are corrupted
462 * <p/>
463 * Make sure you enable this every time you reopen store, otherwise record de-serialization fails.
464 *
465 * @return this builder
466 */
467 public DBMaker checksumEnable(){
468 this._checksumEnabled = true;
469 return this;
470 }
471
472
473 /**
474 * Open store in read-only mode. Any modification attempt will throw
475 * <code>UnsupportedOperationException("Read-only")</code>
476 *
477 * @return this builder
478 */
479 public DBMaker readOnly(){
480 this._readOnly = true;
481 return this;
482 }
483
484
485
486 /**
487 * Set free space reclaim Q. It is value from 0 to 10, indicating how eagerly MapDB
488 * searchs for free space inside store to reuse, before expanding store file.
489 * 0 means that no free space will be reused and store file will just grow (effectively append only).
490 * 10 means that MapDB tries really hard to reuse free space, even if it may hurt performance.
491 * Default value is 5;
492 *
493 *
494 * @return this builder
495 */
496 public DBMaker freeSpaceReclaimQ(int q){
497 if(q<0||q>10) throw new IllegalArgumentException("wrong Q");
498 this._freeSpaceReclaimQ = q;
499 return this;
500 }
501
502 /**
503 * Enables power saving mode.
504 * Typically MapDB runs daemon threads in infinitive cycle with delays and spin locks:
505 * <pre>
506 * while(true){
507 * Thread.sleep(1000);
508 * doSomething();
509 * }
510 *
511 * while(write_finished){
512 * write_chunk;
513 * sleep(10 nanoseconds) //so OS gets chance to finish async writing
514 * }
515 *
516 * </pre>
517 * This brings bit more stability (prevents deadlocks) and some extra speed.
518 * However it causes higher CPU usage then necessary, also CPU wakes-up every
519 * N seconds.
520 * <p>
521 * On power constrained devices (phones, laptops..) trading speed for energy
522 * consumption is not desired. So this settings tells MapDB to prefer
523 * energy efficiency over speed and stability. This is global settings, so
524 * this settings may affects any MapDB part where this settings makes sense
525 * <p>
526 * Currently is used only in {@link AsyncWriteEngine} where power settings
527 * may prevent Background Writer Thread from exiting, if main thread dies.
528 *
529 * @return this builder
530 */
531// public DBMaker powerSavingModeEnable(){
532// this._powerSavingMode = true;
533// return this;
534// }
535
536
537 /** constructs DB using current settings */
538 public DB make(){
539 return new DB(makeEngine());
540 }
541
542
543 public TxMaker makeTxMaker(){
544 return new TxMaker(makeEngine());
545 }
546
547 /** constructs Engine using current settings */
548 public Engine makeEngine(){
549
550
551 if(_readOnly && _file==null)
552 throw new UnsupportedOperationException("Can not open in-memory DB in read-only mode.");
553
554 if(_readOnly && !_file.exists() && !_appendStorage){
555 throw new UnsupportedOperationException("Can not open non-existing file in read-only mode.");
556 }
557
558 Engine engine;
559
560 if(!_appendStorage){
561 Volume.Factory folFac = _file == null?
562 Volume.memoryFactory(_ifInMemoryUseDirectBuffer):
563 Volume.fileFactory(_readOnly, _RAF, _file);
564
565 engine = _journalEnabled ?
566 //TODO add extra params
567 //new StoreWAL(folFac, _freeSpaceReclaimDisabled, _deleteFilesAfterClose, _failOnWrongHeader, _readOnly):
568 //new StoreDirect(folFac, _freeSpaceReclaimDisabled, _deleteFilesAfterClose , _failOnWrongHeader, _readOnly);
569 new StoreWAL(folFac, _readOnly,_deleteFilesAfterClose):
570 new StoreDirect(folFac, _readOnly,_deleteFilesAfterClose);
571 }else{
572 if(_file==null) throw new UnsupportedOperationException("Append Storage format is not supported with in-memory dbs");
573 engine = new StoreAppend(_file, _RAF, _readOnly, !_journalEnabled);
574 }
575
576 if(_checksumEnabled){
577 engine = new ByteTransformEngine(engine, Serializer.CRC32_CHECKSUM);
578 }
579
580 if(_xteaEncryptionKey!=null){
581 engine = new ByteTransformEngine(engine, new EncryptionXTEA(_xteaEncryptionKey));
582 }
583
584
585 if(_compressionEnabled){
586 engine = new ByteTransformEngine(engine, CompressLZF.SERIALIZER);
587 }
588
589
590 AsyncWriteEngine engineAsync = null;
591 if(_asyncWriteEnabled && !_readOnly){
592 engineAsync = new AsyncWriteEngine(engine,!_journalEnabled, _powerSavingMode, _asyncFlushDelay);
593 engine = engineAsync;
594 }
595
596
597 engine = new SnapshotEngine(engine);
598
599 if(_cache == CACHE_DISABLE){
600 //do not wrap engine in cache
601 }if(_cache == CACHE_FIXED_HASH_TABLE){
602 engine = new CacheHashTable(engine,_cacheSize);
603 }else if (_cache == CACHE_HARD_REF){
604 engine = new CacheHardRef(engine,_cacheSize);
605 }else if (_cache == CACHE_WEAK_REF){
606 engine = new CacheWeakSoftRef(engine,true);
607 }else if (_cache == CACHE_SOFT_REF){
608 engine = new CacheWeakSoftRef(engine,false);
609 }else if (_cache == CACHE_LRU){
610 engine = new CacheLRU(engine, _cacheSize);
611 }
612
613
614 if(_readOnly)
615 engine = new ReadOnlyEngine(engine);
616
617 if(engineAsync!=null)
618 engineAsync.setParentEngineReference(engine);
619
620 if(_closeOnJvmShutdown){
621 final Engine engine2 = engine;
622 Runtime.getRuntime().addShutdownHook(new Thread("JDBM shutdown") {
623 @Override
624 public void run() {
625
626 // for JOSM plugin ImageryCache
627 org.openstreetmap.josm.plugins.imagerycache.TileDAOMapDB.dbNotAvailable = true;
628 if(!engine2.isClosed())
629 engine2.close();
630 }
631 });
632 }
633
634 return engine;
635 }
636
637
638
639
640}
Note: See TracBrowser for help on using the repository browser.