Merge lp:~pbeaman/akiban-persistit/fix-dynamic-volumes into lp:akiban-persistit

Proposed by Peter Beaman
Status: Merged
Approved by: Peter Beaman
Approved revision: 383
Merged at revision: 375
Proposed branch: lp:~pbeaman/akiban-persistit/fix-dynamic-volumes
Merge into: lp:akiban-persistit
Diff against target: 1362 lines (+603/-197)
20 files modified
pom.xml (+1/-1)
src/main/java/com/persistit/Buffer.java (+1/-1)
src/main/java/com/persistit/Configuration.java (+52/-1)
src/main/java/com/persistit/Exchange.java (+1/-1)
src/main/java/com/persistit/JournalManager.java (+62/-47)
src/main/java/com/persistit/JournalRecord.java (+3/-3)
src/main/java/com/persistit/JournalTool.java (+1/-1)
src/main/java/com/persistit/Persistit.java (+6/-1)
src/main/java/com/persistit/RecoveryManager.java (+8/-15)
src/main/java/com/persistit/TransactionPlayer.java (+11/-20)
src/main/java/com/persistit/TransactionPlayerSupport.java (+0/-5)
src/main/java/com/persistit/Volume.java (+34/-15)
src/main/java/com/persistit/VolumeSpecification.java (+121/-40)
src/main/java/com/persistit/VolumeStructure.java (+2/-0)
src/main/java/com/persistit/exception/MissingVolumeException.java (+0/-40)
src/main/java/com/persistit/exception/UnderSpecifiedVolumeException.java (+36/-0)
src/test/java/com/persistit/Bug1041293Test.java (+10/-0)
src/test/java/com/persistit/CreateAndDeleteVolumeTest.java (+171/-0)
src/test/java/com/persistit/RecoveryTest.java (+52/-1)
src/test/java/com/persistit/VolumeTest.java (+31/-5)
To merge this branch: bzr merge lp:~pbeaman/akiban-persistit/fix-dynamic-volumes
Reviewer Review Type Date Requested Status
Akiban Build User Needs Fixing
Nathan Williams Approve
Review via email: mp+127848@code.launchpad.net

Description of the change

Corrects a problem in the original JournalManager file format so that dynamically-created volumes are correctly recorded and handled. This change has implications for Akiban Server described below.

The fundamental change is that in the IV (identify volume) record of the Journal, previous versions store only the name of the volume, not the full VolumeSpecification. This works fine when the volumes are fully specified in the Configuration, but does not work at all for volumes created using Persistit#loadVolume(VolumeSpecification). In fact, when such a volume is created the result is an irrecoverable journal recovery failure.

The root cause is a design error in the Journal in which the IV record contains insufficient information to handle all volumes.

The proposed solution is for the IV record to contain the string representation of the VolumeSpecification rather than just the volume name. With the full specification the JournalManager and RecoveryManager can find and open volumes that are not specified in the Configuration.

As part of this proposal, the handling of volumes in JM and RM is cleaned up. In particular, it is no longer the case that the Volume object in a PageNode or on the _handleToVolumeMap in JM is a different object than the Volume actually opened for reading and writing. Instead, there is exactly one Volume object per volume name, and that Volume may be either hollow or open depending on its use. To accomplish this the Persitit#initalizeVolumes() method now finds Volume instances already loaded by the recovery process and uses those instances having names matching the Configuration.

The semantics are a bit complicated:

- (a) a Volume specified statically in the Configuration is matched only by name against a volume specification that has been loaded from the Journal during recovery. This affords the administrator the opportunity to move a volume to a different file system, for example, by moving it and changing the Persistit configuration. However, there is a verification to ensure the volume ID values match, and an Exception is thrown if an attempt to load a different version of a volume having the same name occurs.

- (b) a Volume specified dynamically must exist in the same path and with the same volume ID as specified in the journal. If the volume file itself has been removed since the IV record was written, Persistit receives a VolumeNotFoundException and notes it as a "missing volume" in the AlertMonitor and/or stops depending on the state of the ignoreMissingVolumes configuration property.

The code in this proposal is forward-compatible, meaning that a journal file written before these changes is readable with them. This is because specifying only a name as a VolumeSpecification results in an incomplete VolumeSpecification that is nonetheless capable of being matched against a statically declared volume. Therefore the logic described in (a) is sufficient to match the volume named in the IV record against the volumes defined in the configuration. Therefore no change to existing databases is required handle the upgrade.

On the other hand, by default, the new IV record format written by this proposal is incompatible with older versions of Persistit. This would prevent a customer from upgrading, then downgrading to a previous release. Since we want a period of safe downgrade support in the field, this proposal adds a boolean configuration property "useOldVSpec" that causes IV records to be written in the old format. With this property enabled, journal written by this version are backward-compatible with previous versions of Persistit. The useOldVSpec methods are marked @Deprecated since our plan is to remove this property as soon as all field installations are stable on a release containing the new code.

A couple of new unit tests, one derived from sample code provided in https://bugs.launchpad.net/akiban-persistit/+bug/1045971, have been added. In addition, I have run the entire unit test suite successfully with with useOldVSpec enabled. Of course, the specific test for 1045971 fails as expected in this mode.

A parallel proposal to enable the useOldVSpec parameter is being proposed for Akiban Server.

To post a comment you must log in.
Revision history for this message
Nathan Williams (nwilliams) wrote :

Since this is one way compatible, would a version bump be useful, e.g. 3.2.0? Would make the server side simple to synch as well.

There doesn't appear to be any direct test for to/from string for VolumeSpecification. Since this is directly in the journal and affects database compatibility, should we a be a bit more stringent about that? The edge cases and properties not usually given would be my main concern.

Did Tim have any conclusion from his comment about the useOldVSPec parameter?

Otherwise, everything looks plausible.

review: Needs Information
Revision history for this message
Peter Beaman (pbeaman) wrote :

Good ideas.

Version bumped to 3.2.0.

Modified VolumeSpecification to strengthen validity constraints initial/extension/maximum/Size/Pages, and new tests in VolumeTest.

Revision history for this message
Nathan Williams (nwilliams) wrote :

Thanks for the tweaks. Looks good to me.

review: Approve
Revision history for this message
Akiban Build User (build-akiban) wrote :

There were 2 failures during build/test:

* job persistit-build failed at build number 448: http://172.16.20.104:8080/job/persistit-build/448/

* view must-pass failed: persistit-build is red

review: Needs Fixing
Revision history for this message
Peter Beaman (pbeaman) wrote :

This appears to have been caused by a problem with artifactory. I zapped and refreshed the plugins cache and will now re-Approve to see whether the build works again.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'pom.xml'
2--- pom.xml 2012-09-11 14:06:09 +0000
3+++ pom.xml 2012-10-06 02:46:19 +0000
4@@ -4,7 +4,7 @@
5
6 <groupId>com.akiban</groupId>
7 <artifactId>akiban-persistit</artifactId>
8- <version>3.1.8-SNAPSHOT</version>
9+ <version>3.2.0-SNAPSHOT</version>
10 <packaging>jar</packaging>
11
12 <parent>
13
14=== modified file 'src/main/java/com/persistit/Buffer.java'
15--- src/main/java/com/persistit/Buffer.java 2012-09-28 22:17:31 +0000
16+++ src/main/java/com/persistit/Buffer.java 2012-10-06 02:46:19 +0000
17@@ -4268,7 +4268,7 @@
18 IV.putHandle(bb, volumeHandle);
19 IV.putVolumeId(bb, volume.getId());
20 IV.putTimestamp(bb, 0);
21- IV.putVolumeName(bb, volume.getName());
22+ IV.putVolumeSpecification(bb, volume.getName());
23 bb.position(bb.position() + IV.getLength(bb));
24 identifiedVolumes.add(volume);
25 }
26
27=== modified file 'src/main/java/com/persistit/Configuration.java'
28--- src/main/java/com/persistit/Configuration.java 2012-08-24 14:00:17 +0000
29+++ src/main/java/com/persistit/Configuration.java 2012-10-06 02:46:19 +0000
30@@ -271,6 +271,11 @@
31 public final static String IGNORE_MISSING_VOLUMES_PROPERTY = "ignoremissingvolumes";
32
33 /**
34+ * Property name to enable writing backward-compatible IV records
35+ */
36+ public final static String USE_OLD_VSPEC = "useoldvspec";
37+
38+ /**
39 * Property name to specify the default {@link SplitPolicy}.
40 */
41 public final static String SPLIT_POLICY_PROPERTY_NAME = "splitpolicy";
42@@ -643,6 +648,7 @@
43 private String tmpVolDir;
44 private int tmpVolPageSize;
45 private long tmpVolMaxSize;
46+ private boolean useOldVSpec;
47
48 /**
49 * Construct a <code>Configuration</code> instance. This object may be
50@@ -739,6 +745,7 @@
51 setSysVolume(getProperty(SYSTEM_VOLUME_PROPERTY_NAME, DEFAULT_SYSTEM_VOLUME_NAME));
52 setBufferInventoryEnabled(getBooleanProperty(BUFFER_INVENTORY_PROPERTY_NAME, false));
53 setBufferPreloadEnabled(getBooleanProperty(BUFFER_PRELOAD_PROPERTY_NAME, false));
54+ setUseOldVSpec(getBooleanProperty(USE_OLD_VSPEC, false));
55
56 loadPropertiesBufferSpecifications();
57 loadPropertiesVolumeSpecifications();
58@@ -1877,7 +1884,8 @@
59 /**
60 * Return the value defined by {@link #setIgnoreMissingVolumes(boolean)}
61 *
62- * @return <code>true</code>to enable ignore-missing-volumes mode
63+ * @return <code>true</code> indicates that <i>ignore-missing-volumes</i>
64+ * mode is enabled
65 */
66 public boolean isIgnoreMissingVolumes() {
67 return ignoreMissingVolumes;
68@@ -1903,4 +1911,47 @@
69 public void setIgnoreMissingVolumes(final boolean ignoreMissingVolumes) {
70 this.ignoreMissingVolumes = ignoreMissingVolumes;
71 }
72+
73+ /**
74+ * Return the value defined by {@link #setUseOldVSpec(boolean)}
75+ *
76+ * @return <code>true</code> indicates that <i>use-old-vspec</i> mode is
77+ * enabled
78+ */
79+ @Deprecated
80+ public boolean isUseOldVSpec() {
81+ return useOldVSpec;
82+ }
83+
84+ /**
85+ * <p>
86+ * Control whether Persistit writes old-format volume identifiers into the
87+ * Journal. By default as of version 3.1.8, Persistit writes a complete
88+ * {@link VolumeSpecification} into the journal IV (Identify Volume) record
89+ * rather than just a volume name. This is preferable in almost all cases.
90+ * However, journals written in this new format cannot be read by earlier
91+ * versions of Persistit. To retain the ability to drop back to an earlier
92+ * version of Persistit, enabling <i>use-old-vspec</i> mode causes Persistit
93+ * to write only the volume name rather than the entire VolumeSpecification.
94+ * Journal files created by version 3.1.8 with this mode enabled can be used
95+ * by earlier versions of Persistit. However, doing so prevents volumes
96+ * created dynamically using
97+ * {@link Persistit#loadVolume(VolumeSpecification))} from being recovered
98+ * properly. (See https://bugs.launchpad.net/akiban-persistit/+bug/1045971)
99+ * </p>
100+ * <p>
101+ * This method and configuration parameter is deprecated and will be removed
102+ * after existing sites have been upgraded and are likely not to revert to
103+ * any version of Persistit earlier than 3.1.8
104+ * </p>
105+ *
106+ * @param useOldVSpec
107+ * <code>true</code> to write only the volume name into the
108+ * journal.
109+ */
110+ @Deprecated
111+ public void setUseOldVSpec(final boolean useOldVSpec) {
112+ this.useOldVSpec = useOldVSpec;
113+ }
114+
115 }
116
117=== modified file 'src/main/java/com/persistit/Exchange.java'
118--- src/main/java/com/persistit/Exchange.java 2012-10-02 20:37:24 +0000
119+++ src/main/java/com/persistit/Exchange.java 2012-10-06 02:46:19 +0000
120@@ -1492,7 +1492,7 @@
121 if (doMVCC) {
122 valueToStore = spareValue;
123 final int valueSize = value.getEncodedSize();
124- int retries = VERSIONS_OUT_OF_ORDER_RETRY_COUNT;
125+ final int retries = VERSIONS_OUT_OF_ORDER_RETRY_COUNT;
126
127 for (;;) {
128 try {
129
130=== modified file 'src/main/java/com/persistit/JournalManager.java'
131--- src/main/java/com/persistit/JournalManager.java 2012-09-27 22:17:34 +0000
132+++ src/main/java/com/persistit/JournalManager.java 2012-10-06 02:46:19 +0000
133@@ -60,6 +60,7 @@
134 import com.persistit.exception.PersistitIOException;
135 import com.persistit.exception.PersistitInterruptedException;
136 import com.persistit.exception.RebalanceException;
137+import com.persistit.exception.VolumeNotFoundException;
138 import com.persistit.mxbeans.JournalManagerMXBean;
139 import com.persistit.util.Debug;
140 import com.persistit.util.Util;
141@@ -289,7 +290,6 @@
142 _journalCreatedTime = System.currentTimeMillis();
143 }
144 _closed.set(false);
145-
146 }
147
148 public void startJournal() throws PersistitException {
149@@ -661,7 +661,19 @@
150 if (volume == null) {
151 return null;
152 }
153- return _persistit.getVolume(volume.getName());
154+ if (!volume.isOpened()) {
155+ volume.open(_persistit);
156+ }
157+ return volume;
158+ }
159+
160+ synchronized Volume getVolumeByName(final String volumeName) {
161+ for (final Volume v : _handleToVolumeMap.values()) {
162+ if (volumeName.equals(v.getName())) {
163+ return v;
164+ }
165+ }
166+ return null;
167 }
168
169 @Override
170@@ -1124,7 +1136,11 @@
171 IV.putHandle(_writeBuffer, handle);
172 IV.putVolumeId(_writeBuffer, volume.getId());
173 JournalRecord.putTimestamp(_writeBuffer, epochalTimestamp());
174- IV.putVolumeName(_writeBuffer, volume.getName());
175+ if (_persistit.getConfiguration().isUseOldVSpec()) {
176+ IV.putVolumeSpecification(_writeBuffer, volume.getName());
177+ } else {
178+ IV.putVolumeSpecification(_writeBuffer, volume.getSpecification().toString());
179+ }
180 final int recordSize = JournalRecord.getLength(_writeBuffer);
181 _persistit.getIOMeter().chargeWriteOtherToJournal(recordSize, _currentAddress);
182 advance(recordSize);
183@@ -2440,8 +2456,6 @@
184 int handle = -1;
185
186 for (final Iterator<PageNode> iterator = list.iterator(); iterator.hasNext();) {
187- Volume volumeRef = null;
188-
189 if (_closed.get() && !_copyFast.get() || _appendOnly.get()) {
190 list.clear();
191 break;
192@@ -2451,15 +2465,15 @@
193 iterator.remove();
194 continue;
195 }
196+ pageNode.setOffset(-1);
197 if (pageNode.getVolumeHandle() != handle) {
198 handle = -1;
199- volume = null;
200- // Possibly hollow volume
201- volumeRef = _handleToVolumeMap.get(pageNode.getVolumeHandle());
202- if (volumeRef != null) {
203- // Opened volume, if present
204- volume = _persistit.getVolume(volumeRef.getName());
205- handle = pageNode.getVolumeHandle();
206+ try {
207+ volume = volumeForHandle(pageNode.getVolumeHandle());
208+ handle = volume.getHandle();
209+ } catch (final VolumeNotFoundException vnfe) {
210+ // Deal with this in writeForCopy
211+ continue;
212 }
213 }
214 if (volume == null) {
215@@ -2476,7 +2490,7 @@
216 continue;
217 }
218 pageAddress = readPageBufferFromJournal(stablePageNode, bb);
219- _persistit.getIOMeter().chargeCopyPageFromJournal(volumeRef, pageAddress, volume.getPageSize(),
220+ _persistit.getIOMeter().chargeCopyPageFromJournal(volume, pageAddress, volume.getPageSize(),
221 stablePageNode.getJournalAddress(), urgency());
222 } catch (final PersistitException ioe) {
223 _persistit
224@@ -2501,7 +2515,6 @@
225
226 void writeForCopy(final List<PageNode> list, final ByteBuffer bb) throws PersistitException {
227 Collections.sort(list, PageNode.WRITE_COMPARATOR);
228- Volume volumeRef = null;
229 Volume volume = null;
230 int handle = -1;
231 final Set<Volume> volumes = new HashSet<Volume>();
232@@ -2517,25 +2530,28 @@
233 if (pageNode.getVolumeHandle() != handle) {
234 handle = -1;
235 volume = null;
236- // Possibly hollow volume
237- volumeRef = _handleToVolumeMap.get(pageNode.getVolumeHandle());
238- if (volumeRef != null) {
239- // Opened volume, if present
240- volume = _persistit.getVolume(volumeRef.getName());
241- handle = pageNode.getVolumeHandle();
242- }
243- }
244- if (volume == null) {
245- _persistit.getAlertMonitor().post(
246- new Event(AlertLevel.WARN, _persistit.getLogBase().missingVolume, volumeRef,
247- pageNode.getJournalAddress()), AlertMonitor.MISSING_VOLUME_CATEGORY);
248- if (_ignoreMissingVolume.get()) {
249- _persistit.getLogBase().lostPageFromMissingVolume.log(pageNode.getPageAddress(), volumeRef,
250- pageNode.getJournalAddress());
251- // Not removing the page from the List here will cause
252- // cleanupForCopy to remove it from
253- // the page map.
254- continue;
255+ Volume candidate = null;
256+ try {
257+ candidate = lookupVolumeHandle(pageNode.getVolumeHandle());
258+ if (candidate != null) {
259+ if (!candidate.isOpened()) {
260+ candidate.open(_persistit);
261+ }
262+ handle = pageNode.getVolumeHandle();
263+ volume = candidate;
264+ }
265+ } catch (final VolumeNotFoundException vnfe) {
266+ _persistit.getAlertMonitor().post(
267+ new Event(AlertLevel.WARN, _persistit.getLogBase().missingVolume, candidate,
268+ pageNode.getJournalAddress()), AlertMonitor.MISSING_VOLUME_CATEGORY);
269+ if (_ignoreMissingVolume.get()) {
270+ _persistit.getLogBase().lostPageFromMissingVolume.log(pageNode.getPageAddress(), candidate,
271+ pageNode.getJournalAddress());
272+ // Not removing the page from the List here will cause
273+ // cleanupForCopy to remove it from
274+ // the page map.
275+ continue;
276+ }
277 }
278 }
279 if (volume == null || volume.isClosed()) {
280@@ -2545,8 +2561,6 @@
281 continue;
282 }
283
284- volumeRef.verifyId(volume.getId());
285-
286 final long pageAddress = pageNode.getPageAddress();
287 volume.getStorage().extend(pageAddress);
288 final int pageSize = volume.getPageSize();
289@@ -2624,7 +2638,7 @@
290 // the most recent one that has been checkpointed.
291 //
292 for (PageNode pn = pageNode; pn != null; pn = pn.getPrevious()) {
293- if (pn.getJournalAddress() < recoveryBoundary) {
294+ if (!pn.isInvalid() && pn.getJournalAddress() < recoveryBoundary) {
295 recoveryBoundary = pn.getJournalAddress();
296 }
297 }
298@@ -2725,6 +2739,18 @@
299 return size - to;
300 }
301
302+ synchronized void truncate(final Volume volume, final long timestamp) {
303+ for (final PageNode lastPageNode : _pageMap.values()) {
304+ PageNode pageNode = lastPageNode;
305+ while (pageNode != null) {
306+ if (volume.getHandle() == pageNode.getVolumeHandle() && pageNode.getTimestamp() < timestamp) {
307+ pageNode.invalidate();
308+ }
309+ pageNode = pageNode.getPrevious();
310+ }
311+ }
312+ }
313+
314 private void reportJournalFileCount() {
315 /*
316 * Does not need synchronization since only the JOURNAL_COPIER thread
317@@ -2776,17 +2802,6 @@
318 public Persistit getPersistit() {
319 return _persistit;
320 }
321-
322- @Override
323- public TreeDescriptor handleToTreeDescriptor(final int treeHandle) {
324- return _handleToTreeMap.get(treeHandle);
325- }
326-
327- @Override
328- public Volume handleToVolume(final int volumeHandle) {
329- return _handleToVolumeMap.get(volumeHandle);
330- }
331-
332 }
333
334 class ProactiveRollbackListener implements TransactionPlayerListener {
335
336=== modified file 'src/main/java/com/persistit/JournalRecord.java'
337--- src/main/java/com/persistit/JournalRecord.java 2012-08-24 13:57:19 +0000
338+++ src/main/java/com/persistit/JournalRecord.java 2012-10-06 02:46:19 +0000
339@@ -751,13 +751,13 @@
340 putLong(bb, 20, volumeId);
341 }
342
343- public static String getVolumeName(final ByteBuffer bb) {
344+ public static String getVolumeSpecification(final ByteBuffer bb) {
345 final int length = getLength(bb) - OVERHEAD;
346 return new String(bb.array(), bb.position() + OVERHEAD, length, UTF8);
347 }
348
349- public static void putVolumeName(final ByteBuffer bb, final String volumeName) {
350- final byte[] stringBytes = volumeName.getBytes(UTF8);
351+ public static void putVolumeSpecification(final ByteBuffer bb, final String volumeSpec) {
352+ final byte[] stringBytes = volumeSpec.getBytes(UTF8);
353 System.arraycopy(stringBytes, 0, bb.array(), bb.position() + OVERHEAD, stringBytes.length);
354 putLength(bb, OVERHEAD + stringBytes.length);
355 }
356
357=== modified file 'src/main/java/com/persistit/JournalTool.java'
358--- src/main/java/com/persistit/JournalTool.java 2012-08-24 13:57:19 +0000
359+++ src/main/java/com/persistit/JournalTool.java 2012-10-06 02:46:19 +0000
360@@ -732,7 +732,7 @@
361 read(address, recordSize);
362 final int handle = IV.getHandle(_readBuffer);
363 final long id = IV.getVolumeId(_readBuffer);
364- final String name = IV.getVolumeName(_readBuffer);
365+ final String name = IV.getVolumeSpecification(_readBuffer);
366 start(address, timestamp, "IV", recordSize);
367 appendf(" handle %05d id %,22d name %s", handle, id, name);
368 flush();
369
370=== modified file 'src/main/java/com/persistit/Persistit.java'
371--- src/main/java/com/persistit/Persistit.java 2012-09-12 22:02:22 +0000
372+++ src/main/java/com/persistit/Persistit.java 2012-10-06 02:46:19 +0000
373@@ -663,8 +663,13 @@
374
375 void initializeVolumes() throws PersistitException {
376 for (final VolumeSpecification volumeSpecification : _configuration.getVolumeList()) {
377+ Volume volume = _journalManager.getVolumeByName(volumeSpecification.getName());
378+ if (volume == null) {
379+ volume = new Volume(volumeSpecification);
380+ } else {
381+ volume.overwriteSpecification(volumeSpecification);
382+ }
383 _logBase.openVolume.log(volumeSpecification.getName(), volumeSpecification.getAbsoluteFile());
384- final Volume volume = new Volume(volumeSpecification);
385 volume.open(this);
386 }
387 }
388
389=== modified file 'src/main/java/com/persistit/RecoveryManager.java'
390--- src/main/java/com/persistit/RecoveryManager.java 2012-08-24 13:57:19 +0000
391+++ src/main/java/com/persistit/RecoveryManager.java 2012-10-06 02:46:19 +0000
392@@ -360,17 +360,6 @@
393 public Persistit getPersistit() {
394 return _persistit;
395 }
396-
397- @Override
398- public TreeDescriptor handleToTreeDescriptor(final int treeHandle) {
399- return _handleToTreeMap.get(treeHandle);
400- }
401-
402- @Override
403- public Volume handleToVolume(final int volumeHandle) {
404- return _handleToVolumeMap.get(volumeHandle);
405- }
406-
407 }
408
409 static File[] files(final String pathName) {
410@@ -986,14 +975,18 @@
411 }
412 read(address, recordSize);
413 final Integer handle = Integer.valueOf(IV.getHandle(_readBuffer));
414- final String name = IV.getVolumeName(_readBuffer);
415- final long volumeId = IV.getVolumeId(_readBuffer);
416- final Volume volume = new Volume(name, volumeId);
417+ final long id = IV.getVolumeId(_readBuffer);
418+ final String specification = IV.getVolumeSpecification(_readBuffer);
419+ final VolumeSpecification vs = new VolumeSpecification(specification);
420+ vs.setCreate(false);
421+ vs.setCreateOnly(false);
422+ final Volume volume = new Volume(vs);
423+ volume.setId(id);
424
425 _handleToVolumeMap.put(handle, volume);
426 _volumeToHandleMap.put(volume, handle);
427
428- _persistit.getLogBase().recoveryRecord.log("IV", addressToString(address, timestamp), name, timestamp);
429+ _persistit.getLogBase().recoveryRecord.log("IV", addressToString(address, timestamp), vs.getName(), timestamp);
430 }
431
432 /**
433
434=== modified file 'src/main/java/com/persistit/TransactionPlayer.java'
435--- src/main/java/com/persistit/TransactionPlayer.java 2012-09-08 20:43:15 +0000
436+++ src/main/java/com/persistit/TransactionPlayer.java 2012-10-06 02:46:19 +0000
437@@ -36,8 +36,8 @@
438 import com.persistit.JournalRecord.SR;
439 import com.persistit.JournalRecord.TX;
440 import com.persistit.exception.CorruptJournalException;
441-import com.persistit.exception.MissingVolumeException;
442 import com.persistit.exception.PersistitException;
443+import com.persistit.exception.VolumeNotFoundException;
444
445 class TransactionPlayer {
446
447@@ -245,7 +245,7 @@
448 + addressToString(address));
449 }
450 }
451- } catch (final MissingVolumeException mve) {
452+ } catch (final VolumeNotFoundException vnfe) {
453 final Persistit db = _support.getPersistit();
454 if (db.getJournalManager().isIgnoreMissingVolumes()) {
455 /*
456@@ -253,13 +253,16 @@
457 * Alert, but allow recovery or rollback to continue.
458 */
459 db.getAlertMonitor().post(
460- new Event(AlertLevel.WARN, db.getLogBase().missingVolume, mve.getVolumeName(), address
461+ new Event(AlertLevel.WARN, db.getLogBase().missingVolume, vnfe.getMessage(), address
462 + position - start), AlertMonitor.MISSING_VOLUME_CATEGORY);
463 ignoredUpdates.incrementAndGet();
464 } else {
465 failedUpdates.incrementAndGet();
466- throw mve;
467+ throw vnfe;
468 }
469+ } catch (final PersistitException e) {
470+ failedUpdates.incrementAndGet();
471+ throw e;
472 }
473 position += innerSize;
474 }
475@@ -274,29 +277,16 @@
476 }
477
478 private Exchange getExchange(final int treeHandle, final long from, final long timestamp) throws PersistitException {
479- final TreeDescriptor td = _support.handleToTreeDescriptor(treeHandle);
480+ final TreeDescriptor td = _support.getPersistit().getJournalManager().lookupTreeHandle(treeHandle);
481 if (td == null) {
482 throw new CorruptJournalException("Tree handle " + treeHandle + " is undefined at "
483 + addressToString(from, timestamp));
484 }
485- final Volume volumeRef = _support.handleToVolume(td.getVolumeHandle());
486- Volume volume;
487- if (volumeRef == null) {
488+ final Volume volume = _support.getPersistit().getJournalManager().volumeForHandle(td.getVolumeHandle());
489+ if (volume == null) {
490 throw new CorruptJournalException("Volume handle " + td.getVolumeHandle() + " is undefined at "
491 + addressToString(from, timestamp));
492 }
493-
494- if (volumeRef.isOpened()) {
495- volume = volumeRef;
496- } else {
497- volume = _support.getPersistit().getVolume(volumeRef.getName());
498- if (volume == null) {
499- throw new MissingVolumeException("No matching Volume found for journal reference " + volumeRef + " at "
500- + addressToString(from, timestamp), volumeRef.getName());
501- }
502- }
503- volume.verifyId(volume.getId());
504-
505 if (VolumeStructure.DIRECTORY_TREE_NAME.equals(td.getTreeName())) {
506 return volume.getStructure().directoryExchange();
507 } else {
508@@ -321,4 +311,5 @@
509 long getFailedUpdates() {
510 return failedUpdates.get();
511 }
512+
513 }
514
515=== modified file 'src/main/java/com/persistit/TransactionPlayerSupport.java'
516--- src/main/java/com/persistit/TransactionPlayerSupport.java 2012-08-24 13:57:19 +0000
517+++ src/main/java/com/persistit/TransactionPlayerSupport.java 2012-10-06 02:46:19 +0000
518@@ -17,17 +17,12 @@
519
520 import java.nio.ByteBuffer;
521
522-import com.persistit.JournalManager.TreeDescriptor;
523 import com.persistit.exception.PersistitException;
524 import com.persistit.exception.PersistitIOException;
525
526 interface TransactionPlayerSupport {
527 Persistit getPersistit();
528
529- TreeDescriptor handleToTreeDescriptor(int treeHandle);
530-
531- Volume handleToVolume(int volumeHandle);
532-
533 void read(long address, int size) throws PersistitIOException;
534
535 ByteBuffer getReadBuffer();
536
537=== modified file 'src/main/java/com/persistit/Volume.java'
538--- src/main/java/com/persistit/Volume.java 2012-08-31 22:19:04 +0000
539+++ src/main/java/com/persistit/Volume.java 2012-10-06 02:46:19 +0000
540@@ -24,6 +24,7 @@
541 import com.persistit.exception.PersistitException;
542 import com.persistit.exception.ReadOnlyVolumeException;
543 import com.persistit.exception.TruncateVolumeException;
544+import com.persistit.exception.UnderSpecifiedVolumeException;
545 import com.persistit.exception.VolumeAlreadyExistsException;
546 import com.persistit.exception.VolumeClosedException;
547 import com.persistit.exception.VolumeNotFoundException;
548@@ -94,6 +95,7 @@
549 Volume(final String name, final long id) {
550 _name = name;
551 _id = id;
552+ _specification = new VolumeSpecification(name);
553 }
554
555 /**
556@@ -144,13 +146,18 @@
557 if (_specification != null) {
558 throw new IllegalStateException("Volume " + this + " already has a VolumeSpecification");
559 }
560- if (specification.getName().equals(_name) && specification.getId() == _id) {
561+ if (specification.getName().equals(_name) && (specification.getId() == _id || specification.getId() == 0)) {
562 _specification = specification;
563 } else {
564 throw new IllegalStateException("Volume " + this + " is incompatible with " + specification);
565 }
566 }
567
568+ void overwriteSpecification(final VolumeSpecification specification) {
569+ _specification = null;
570+ setSpecification(specification);
571+ }
572+
573 void closing() {
574 _closing.set(true);
575 }
576@@ -238,8 +245,7 @@
577 //
578 // BufferPool#invalidate may fail and return false if other
579 // threads hold claims on pages of this volume. In that case
580- // we
581- // need to back off all locks and retry
582+ // we need to back off all locks and retry
583 //
584 if (getStructure().getPool().invalidate(this)) {
585 getStructure().truncate();
586@@ -430,8 +436,11 @@
587 if (_storage != null) {
588 throw new IllegalStateException("This volume has already been opened");
589 }
590+ if (_specification.getPageSize() <= 0) {
591+ throw new UnderSpecifiedVolumeException(getName());
592+ }
593 if (persistit.getBufferPool(_specification.getPageSize()) == null) {
594- throw new IllegalStateException("There is no buffer pool for pages of volume " + _specification);
595+ throw new IllegalStateException(getName());
596 }
597 final boolean exists = VolumeHeader.verifyVolumeHeader(_specification, persistit.getCurrentTimestamp());
598
599@@ -439,17 +448,27 @@
600 _storage = new VolumeStorageV2(persistit, this);
601 _statistics = new VolumeStatistics();
602
603- if (exists) {
604- if (_specification.isCreateOnly()) {
605- throw new VolumeAlreadyExistsException(_specification.getPath());
606- }
607- _storage.open();
608- } else {
609- if (!_specification.isCreate()) {
610- throw new VolumeNotFoundException(_specification.getPath());
611- }
612- _storage.create();
613-
614+ boolean opened = false;
615+ try {
616+ if (exists) {
617+ if (_specification.isCreateOnly()) {
618+ throw new VolumeAlreadyExistsException(_specification.getPath());
619+ }
620+ _storage.open();
621+ opened = true;
622+ } else {
623+ if (!_specification.isCreate()) {
624+ throw new VolumeNotFoundException(_specification.getPath());
625+ }
626+ _storage.create();
627+ opened = true;
628+ }
629+ } finally {
630+ if (!opened) {
631+ _structure = null;
632+ _storage = null;
633+ _statistics = null;
634+ }
635 }
636 persistit.addVolume(this);
637 }
638
639=== modified file 'src/main/java/com/persistit/VolumeSpecification.java'
640--- src/main/java/com/persistit/VolumeSpecification.java 2012-08-31 22:19:04 +0000
641+++ src/main/java/com/persistit/VolumeSpecification.java 2012-10-06 02:46:19 +0000
642@@ -24,6 +24,7 @@
643
644 import com.persistit.exception.InvalidVolumeSpecificationException;
645 import com.persistit.exception.VolumeAlreadyExistsException;
646+import com.persistit.util.Util;
647
648 /**
649 * A structure holding all the specification details for a {@link Volume}.
650@@ -53,6 +54,7 @@
651 private boolean readOnly = false;
652 private boolean create = false;
653 private boolean createOnly = false;
654+ private boolean aliased = false;
655
656 private int pageSize = -1;
657 private int version = -1;
658@@ -99,7 +101,11 @@
659 final long maximumPages, final long extensionPages, final boolean create, final boolean createOnly,
660 final boolean readOnly) {
661 this.path = path;
662- this.name = name == null ? nameFromFile(new File(path)) : name;
663+ if (name == null) {
664+ this.name = nameFromFile(new File(path));
665+ } else {
666+ setName(name);
667+ }
668 this.pageSize = pageSize;
669 this.initialPages = initialPages;
670 this.maximumPages = maximumPages;
671@@ -181,7 +187,7 @@
672 } else if (ATTR_NAME.equals(attr) || ATTR_ALIAS.equals(attr)) {
673 final String valueString = innerTokenizer.nextToken().trim();
674 if (valueString != null && !valueString.isEmpty()) {
675- name = valueString;
676+ setName(valueString);
677 }
678 } else {
679 final String valueString = innerTokenizer.nextToken().trim();
680@@ -302,6 +308,10 @@
681 return path;
682 }
683
684+ public void setPath(final String path) {
685+ this.path = path;
686+ }
687+
688 public File getAbsoluteFile() {
689 return new File(path).getAbsoluteFile();
690 }
691@@ -310,18 +320,39 @@
692 return name;
693 }
694
695+ public void setName(final String name) {
696+ this.aliased = !nameFromFile(new File(path)).equals(name);
697+ this.name = name;
698+ }
699+
700 public boolean isReadOnly() {
701 return readOnly;
702 }
703
704+ public void setReadOnly(final boolean readOnly) {
705+ this.readOnly = readOnly;
706+ }
707+
708+ public boolean isAliased() {
709+ return aliased;
710+ }
711+
712 public boolean isCreate() {
713 return create || createOnly;
714 }
715
716+ public void setCreate(final boolean create) {
717+ this.create = create;
718+ }
719+
720 public boolean isCreateOnly() {
721 return createOnly;
722 }
723
724+ public void setCreateOnly(final boolean createOnly) {
725+ this.createOnly = createOnly;
726+ }
727+
728 public int getPageSize() {
729 return pageSize;
730 }
731@@ -330,26 +361,56 @@
732 return initialPages;
733 }
734
735+ void setInitialPages(final long initialPages) {
736+ this.initialSize = sizeFromPages(initialPages);
737+ this.initialPages = initialPages;
738+ }
739+
740 public long getExtensionPages() {
741 return extensionPages;
742 }
743
744+ void setExtensionPages(final long extensionPages) {
745+ this.extensionSize = sizeFromPages(extensionPages);
746+ this.extensionPages = extensionPages;
747+ }
748+
749 public long getMaximumPages() {
750 return maximumPages;
751 }
752
753+ void setMaximumPages(final long maximumPages) {
754+ this.maximumSize = sizeFromPages(maximumPages);
755+ this.maximumPages = maximumPages;
756+ }
757+
758 public long getInitialSize() {
759 return initialSize;
760 }
761
762+ public void setInitialSize(final long initialSize) {
763+ this.initialPages = pagesFromSize(initialSize);
764+ this.initialSize = roundSize(initialSize);
765+ }
766+
767 public long getExtensionSize() {
768 return extensionSize;
769 }
770
771+ public void setExtensionSize(final long extensionSize) {
772+ this.extensionPages = pagesFromSize(extensionSize);
773+ this.extensionSize = roundSize(extensionSize);
774+ }
775+
776 public long getMaximumSize() {
777 return maximumSize;
778 }
779
780+ public void setMaximumSize(final long maximumSize) {
781+ this.maximumPages = pagesFromSize(maximumSize);
782+ this.maximumSize = roundSize(maximumSize);
783+ }
784+
785 public int getVersion() {
786 return version;
787 }
788@@ -362,18 +423,17 @@
789 public String toString() {
790 final StringBuilder sb = new StringBuilder();
791 sb.append(path);
792- sb.append(',').append(ATTR_NAME).append(':').append(name);
793- sb.append(',').append(ATTR_PAGE_SIZE).append(':').append(pageSize);
794-
795- if (initialPages >= 0) {
796- sb.append(',').append(ATTR_INITIAL_SIZE).append(':').append(ds(initialPages));
797- }
798- if (maximumPages >= 0) {
799- sb.append(',').append(ATTR_MAXIMUM_SIZE).append(':').append(ds(maximumPages));
800- }
801- if (extensionPages >= 0) {
802- sb.append(',').append(ATTR_EXTENSION_SIZE).append(':').append(ds(extensionPages));
803- }
804+ if (aliased) {
805+ sb.append(',').append(ATTR_NAME).append(':').append(name);
806+ }
807+ if (pageSize > 0) {
808+ sb.append(',').append(ATTR_PAGE_SIZE).append(':').append(pageSize);
809+ }
810+
811+ appendSize(sb, ATTR_INITIAL_SIZE, ATTR_INITIAL_PAGES, initialSize, initialPages);
812+ appendSize(sb, ATTR_MAXIMUM_SIZE, ATTR_MAXIMUM_PAGES, maximumSize, maximumPages);
813+ appendSize(sb, ATTR_EXTENSION_SIZE, ATTR_EXTENSION_PAGES, extensionSize, extensionPages);
814+
815 if (readOnly) {
816 sb.append(',').append(ATTR_READONLY);
817 }
818@@ -385,36 +445,57 @@
819 return sb.toString();
820 }
821
822- private String ds(final long pages) {
823- return Configuration.displayableLongValue(pages * pageSize);
824+ private void appendSize(final StringBuilder sb, final String sizeName, final String pagesName, final long size,
825+ final long pages) {
826+ if (pageSize <= 0) {
827+ if (pages > 0) {
828+ sb.append(',').append(pagesName).append(':').append(ds(pages));
829+ } else if (size > 0) {
830+ sb.append(',').append(sizeName).append(':').append(ds(size));
831+ }
832+ } else if (pages > 0) {
833+ sb.append(',').append(sizeName).append(':').append(ds(pages * pageSize));
834+ } else if (size > 0) {
835+ sb.append(',').append(sizeName).append(':').append(ds(size));
836+ }
837+ }
838+
839+ @Override
840+ public boolean equals(final Object object) {
841+ if (!(object instanceof VolumeSpecification)) {
842+ return false;
843+ }
844+ final VolumeSpecification v = (VolumeSpecification) object;
845+ return path.equals(v.path) && name.equals(v.name) && readOnly == v.readOnly && create == v.create
846+ && createOnly == v.createOnly && aliased == v.aliased && pageSize == v.pageSize && version == v.version
847+ && id == v.id && initialPages == v.initialPages && initialSize == v.initialSize
848+ && extensionPages == v.extensionPages && extensionSize == v.extensionSize
849+ && maximumPages == v.maximumPages && maximumSize == v.maximumSize;
850+ }
851+
852+ private String ds(final long s) {
853+ return Configuration.displayableLongValue(s);
854 }
855
856 public String summary() {
857 return name + "(" + path + ")";
858 }
859
860- /**
861- * @param initialPages
862- * the initialPages to set
863- */
864- void setInitialPages(final long initialPages) {
865- this.initialPages = initialPages;
866- }
867-
868- /**
869- * @param extensionPages
870- * the extensionPages to set
871- */
872- void setExtensionPages(final long extensionPages) {
873- this.extensionPages = extensionPages;
874- }
875-
876- /**
877- * @param maximumPages
878- * the maximumPages to set
879- */
880- void setMaximumPages(final long maximumPages) {
881- this.maximumPages = maximumPages;
882- }
883-
884+ private long sizeFromPages(final long pages) {
885+ Util.rangeCheck(pages, 0, Long.MAX_VALUE / Buffer.MAX_BUFFER_SIZE);
886+ return pageSize > 0 ? pages * pageSize : 0;
887+ }
888+
889+ private long pagesFromSize(final long size) {
890+ Util.rangeCheck(size, 0, Long.MAX_VALUE);
891+ return pageSize > 0 ? size / pageSize : 0;
892+ }
893+
894+ private long roundSize(final long size) {
895+ if (pageSize > 0) {
896+ return (size / pageSize) * pageSize;
897+ } else {
898+ return size;
899+ }
900+ }
901 }
902
903=== modified file 'src/main/java/com/persistit/VolumeStructure.java'
904--- src/main/java/com/persistit/VolumeStructure.java 2012-09-28 21:39:44 +0000
905+++ src/main/java/com/persistit/VolumeStructure.java 2012-10-06 02:46:19 +0000
906@@ -117,6 +117,7 @@
907 }
908
909 synchronized void truncate() {
910+ final long timestamp = _persistit.getTimestampAllocator().updateTimestamp();
911 for (final WeakReference<Tree> treeRef : _treeNameHashMap.values()) {
912 final Tree tree = treeRef.get();
913 if (tree != null) {
914@@ -124,6 +125,7 @@
915 }
916 }
917 _treeNameHashMap.clear();
918+ _persistit.getJournalManager().truncate(_volume, timestamp);
919 }
920
921 Exchange directoryExchange() {
922
923=== removed file 'src/main/java/com/persistit/exception/MissingVolumeException.java'
924--- src/main/java/com/persistit/exception/MissingVolumeException.java 2012-08-24 13:57:19 +0000
925+++ src/main/java/com/persistit/exception/MissingVolumeException.java 1970-01-01 00:00:00 +0000
926@@ -1,40 +0,0 @@
927-/**
928- * Copyright © 2012 Akiban Technologies, Inc. All rights reserved.
929- *
930- * This program and the accompanying materials are made available
931- * under the terms of the Eclipse Public License v1.0 which
932- * accompanies this distribution, and is available at
933- * http://www.eclipse.org/legal/epl-v10.html
934- *
935- * This program may also be available under different license terms.
936- * For more information, see www.akiban.com or contact licensing@akiban.com.
937- *
938- * Contributors:
939- * Akiban Technologies, Inc.
940- */
941-
942-package com.persistit.exception;
943-
944-/**
945- * Thrown if the journal files refer to a volume that is no longer present in
946- * the system. Generally this condition is irrecoverable because without the
947- * missing volume a consistent database state cannot be restored. However, in
948- * the event the removal of the volume is intentional, it is possible to specify
949- * a mode in which pages and transactions destined for missing volumes are
950- * ignored.
951- *
952- * @version 1.0
953- */
954-public class MissingVolumeException extends CorruptJournalException {
955- private static final long serialVersionUID = -9014051945087375523L;
956- private final String _volumeName;
957-
958- public MissingVolumeException(final String msg, final String volumeName) {
959- super(msg);
960- _volumeName = volumeName;
961- }
962-
963- public String getVolumeName() {
964- return _volumeName;
965- }
966-}
967
968=== added file 'src/main/java/com/persistit/exception/UnderSpecifiedVolumeException.java'
969--- src/main/java/com/persistit/exception/UnderSpecifiedVolumeException.java 1970-01-01 00:00:00 +0000
970+++ src/main/java/com/persistit/exception/UnderSpecifiedVolumeException.java 2012-10-06 02:46:19 +0000
971@@ -0,0 +1,36 @@
972+/**
973+ * Copyright © 2005-2012 Akiban Technologies, Inc. All rights reserved.
974+ *
975+ * This program and the accompanying materials are made available
976+ * under the terms of the Eclipse Public License v1.0 which
977+ * accompanies this distribution, and is available at
978+ * http://www.eclipse.org/legal/epl-v10.html
979+ *
980+ * This program may also be available under different license terms.
981+ * For more information, see www.akiban.com or contact licensing@akiban.com.
982+ *
983+ * Contributors:
984+ * Akiban Technologies, Inc.
985+ */
986+
987+package com.persistit.exception;
988+
989+import com.persistit.VolumeSpecification;
990+
991+/**
992+ * Thrown when the Persistit attempts to open a Volume with an incomplete
993+ * {@link VolumeSpecification}, for example, with a missing page size.
994+ *
995+ * @version 1.0
996+ */
997+public class UnderSpecifiedVolumeException extends VolumeNotFoundException {
998+ private static final long serialVersionUID = -5547869858193325359L;
999+
1000+ public UnderSpecifiedVolumeException() {
1001+ super();
1002+ }
1003+
1004+ public UnderSpecifiedVolumeException(final String msg) {
1005+ super(msg);
1006+ }
1007+}
1008
1009=== modified file 'src/test/java/com/persistit/Bug1041293Test.java'
1010--- src/test/java/com/persistit/Bug1041293Test.java 2012-09-12 20:36:27 +0000
1011+++ src/test/java/com/persistit/Bug1041293Test.java 2012-10-06 02:46:19 +0000
1012@@ -17,6 +17,8 @@
1013
1014 import org.junit.Test;
1015
1016+import com.persistit.exception.UnderSpecifiedVolumeException;
1017+
1018 /**
1019 * https://bugs.launchpad.net/akiban-persistit/+bug/1041293
1020 *
1021@@ -29,6 +31,14 @@
1022
1023 public class Bug1041293Test extends PersistitUnitTestCase {
1024
1025+ @Test(expected = UnderSpecifiedVolumeException.class)
1026+ public void underSpecifiedVolume() throws Exception {
1027+ final Configuration config = _persistit.getConfiguration();
1028+ final VolumeSpecification vspec = config.volumeSpecification("${datapath}/test");
1029+ final Volume volume = _persistit.loadVolume(vspec);
1030+ volume.open(_persistit);
1031+ }
1032+
1033 @Test(expected = IllegalStateException.class)
1034 public void mismatchedVolumeSpecificationNPE() throws Exception {
1035 final Configuration config = _persistit.getConfiguration();
1036
1037=== added file 'src/test/java/com/persistit/CreateAndDeleteVolumeTest.java'
1038--- src/test/java/com/persistit/CreateAndDeleteVolumeTest.java 1970-01-01 00:00:00 +0000
1039+++ src/test/java/com/persistit/CreateAndDeleteVolumeTest.java 2012-10-06 02:46:19 +0000
1040@@ -0,0 +1,171 @@
1041+/**
1042+ * Copyright © 2012 Akiban Technologies, Inc. All rights reserved.
1043+ *
1044+ * This program and the accompanying materials are made available
1045+ * under the terms of the Eclipse Public License v1.0 which
1046+ * accompanies this distribution, and is available at
1047+ * http://www.eclipse.org/legal/epl-v10.html
1048+ *
1049+ * This program may also be available under different license terms.
1050+ * For more information, see www.akiban.com or contact licensing@akiban.com.
1051+ *
1052+ * Contributors:
1053+ * Akiban Technologies, Inc.
1054+ */
1055+
1056+package com.persistit;
1057+
1058+import static com.persistit.unit.UnitTestProperties.DATA_PATH;
1059+import static org.junit.Assert.assertEquals;
1060+import static org.junit.Assert.assertTrue;
1061+
1062+import org.junit.Ignore;
1063+import org.junit.Test;
1064+
1065+public class CreateAndDeleteVolumeTest extends PersistitUnitTestCase {
1066+
1067+ /**
1068+ * Test for https://bugs.launchpad.net/akiban-persistit/+bug/1045971
1069+ *
1070+ * Dynamically loaded volumes not recovered "cleanly"
1071+ *
1072+ * When a program creates new volumes using VolumeSpecifications, they
1073+ * subsequently cause a "missing volume" warning from the journal copier and
1074+ * prevent the removal of journal files containing the references. The
1075+ * journal files accumulate seemingly without end, and each initialization
1076+ * of the database seems to process more and more of the old transactions.
1077+ *
1078+ * With thanks to jb5
1079+ *
1080+ * @throws Exception
1081+ */
1082+ @Test
1083+ public void recoverDynamicVolumes() throws Exception {
1084+ VolumeSpecification volumeSpec;
1085+ final Configuration configuration = _persistit.getConfiguration();
1086+ _persistit.close();
1087+ int remainingJournalFiles = 0;
1088+
1089+ for (int i = 5; --i >= 0;) {
1090+ final Persistit db = new Persistit();
1091+ try {
1092+ db.initialize(configuration);
1093+ volumeSpec = new VolumeSpecification(DATA_PATH + "/hwdemo" + i, null, 16384, 1, 1000, 1, true, false,
1094+ false);
1095+ db.loadVolume(volumeSpec);
1096+ final Exchange dbex = db.getExchange("hwdemo" + i, "greetings", true);
1097+ dbex.getKey().append("Hello");
1098+ dbex.getValue().put("World");
1099+ dbex.store();
1100+ dbex.getKey().to(Key.BEFORE);
1101+ db.releaseExchange(dbex);
1102+ } finally {
1103+ if (i == 0) {
1104+ db.copyBackPages();
1105+ }
1106+ remainingJournalFiles = db.getJournalManager().getJournalFileCount();
1107+ db.close();
1108+ }
1109+ }
1110+ assertEquals("Should be only one remaining journal file", 1, remainingJournalFiles);
1111+ }
1112+
1113+ @Test
1114+ public void useOldVSpecInducesExpectedFailure() throws Exception {
1115+ VolumeSpecification volumeSpec;
1116+ final Configuration configuration = _persistit.getConfiguration();
1117+ _persistit.close();
1118+ int remainingJournalFiles = 0;
1119+ configuration.setUseOldVSpec(true);
1120+
1121+ for (int i = 5; --i >= 0;) {
1122+ final Persistit db = new Persistit();
1123+ try {
1124+ db.initialize(configuration);
1125+ volumeSpec = new VolumeSpecification(DATA_PATH + "/hwdemo" + i, null, 16384, 1, 1000, 1, true, false,
1126+ false);
1127+ db.loadVolume(volumeSpec);
1128+ final Exchange dbex = db.getExchange("hwdemo" + i, "greetings", true);
1129+ dbex.getKey().append("Hello");
1130+ dbex.getValue().put("World");
1131+ dbex.store();
1132+ dbex.getKey().to(Key.BEFORE);
1133+ db.releaseExchange(dbex);
1134+ } finally {
1135+ if (i == 0) {
1136+ db.copyBackPages();
1137+ }
1138+ remainingJournalFiles = db.getJournalManager().getJournalFileCount();
1139+ db.close();
1140+ }
1141+ }
1142+ assertTrue("Should be only one remaining journal file", remainingJournalFiles > 1);
1143+
1144+ }
1145+
1146+ /**
1147+ * Test for bug https://bugs.launchpad.net/akiban-persistit/+bug/1045983
1148+ *
1149+ * Truncating a dynamically created volume results in corrupted journal If
1150+ * you dynamically load a volume, truncate it (without adding any trees),
1151+ * and then close it, the next time the database is initialized a fatal
1152+ * exception is thrown:
1153+ *
1154+ * <code><pre>
1155+ *
1156+ * [JOURNAL_COPIER] WARNING Missing volume truncated referenced at journal address 364
1157+ * [main] WARNING Missing volume truncated referenced at journal address 17,004 (6 similar occurrences in 0 seconds)
1158+ * Exception in thread "main" com.persistit.exception.InvalidPageAddressException: Page 1 out of bounds [0-1]
1159+ * at com.persistit.VolumeStorageV2.readPage(VolumeStorageV2.java:426)
1160+ * at com.persistit.Buffer.load(Buffer.java:456)
1161+ * at com.persistit.BufferPool.get(BufferPool.java:780)
1162+ * at com.persistit.Tree.setRootPageAddress(Tree.java:203)
1163+ * at com.persistit.VolumeStructure.init(VolumeStructure.java:70)
1164+ * at com.persistit.VolumeStorageV2.open(VolumeStorageV2.java:217)
1165+ * at com.persistit.Volume.open(Volume.java:442)
1166+ * at com.persistit.Persistit.loadVolume(Persistit.java:1066)
1167+ * at Truncate.main(Truncate.java:30)
1168+ *
1169+ * @throws Exception
1170+ *
1171+ * </pre></code>
1172+ *
1173+ * This test is currently disabled pending a fix.
1174+ *
1175+ */
1176+ @Test
1177+ @Ignore
1178+ public void truncateDynamicVolumes() throws Exception {
1179+
1180+ VolumeSpecification volumeSpec;
1181+ final Configuration configuration = _persistit.getConfiguration();
1182+ _persistit.close();
1183+
1184+ final Persistit db = new Persistit();
1185+
1186+ for (int i = 0; i < 2; i++) {
1187+ try {
1188+ db.initialize(configuration);
1189+ volumeSpec = new VolumeSpecification(DATA_PATH + "/truncated", null, 16384, 1, 1000, 1, true, false,
1190+ false);
1191+ final Volume volume = db.loadVolume(volumeSpec);
1192+ volume.truncate();
1193+ // the following may be omitted, and the problem still exhibited
1194+ final Exchange dbex = db.getExchange("truncated", "greetings", true);
1195+ dbex.getKey().append("ave");
1196+ dbex.getValue().put("mundus");
1197+ dbex.store();
1198+ dbex.getKey().to(Key.BEFORE);
1199+ while (dbex.next()) {
1200+ System.out.println(dbex.getKey().reset().decode() + " " + dbex.getValue().get());
1201+ }
1202+ db.releaseExchange(dbex);
1203+ // the preceding may be omitted, and the problem still exhibited
1204+ } finally {
1205+ db.close();
1206+ }
1207+ }
1208+
1209+ }
1210+
1211+}
1212
1213=== modified file 'src/test/java/com/persistit/RecoveryTest.java'
1214--- src/test/java/com/persistit/RecoveryTest.java 2012-08-24 13:57:19 +0000
1215+++ src/test/java/com/persistit/RecoveryTest.java 2012-10-06 02:46:19 +0000
1216@@ -18,6 +18,7 @@
1217 import static org.junit.Assert.assertEquals;
1218 import static org.junit.Assert.assertTrue;
1219
1220+import java.io.File;
1221 import java.util.HashMap;
1222 import java.util.HashSet;
1223 import java.util.Iterator;
1224@@ -498,7 +499,57 @@
1225 for (int i = 0; i < count + 100; i++) {
1226 assertEquals(i < count, exchange.to(i).isValueDefined());
1227 }
1228-
1229+ }
1230+
1231+ /*
1232+ * Deprecated because the useOldVSpec param will go away soon.
1233+ */
1234+ @Test
1235+ @Deprecated
1236+ public void testNormalRestartUseOldVSpec() throws Exception {
1237+ final Configuration config = _persistit.getConfiguration();
1238+ _persistit.close();
1239+ UnitTestProperties.cleanUpDirectory(new File(UnitTestProperties.DATA_PATH));
1240+ config.setUseOldVSpec(true);
1241+ _persistit = new Persistit();
1242+ _persistit.initialize(config);
1243+
1244+ final JournalManager jman = _persistit.getJournalManager();
1245+ Exchange exchange = _persistit.getExchange(_volumeName, "RecoveryTest", true);
1246+ exchange.getValue().put(RED_FOX);
1247+ int count = 0;
1248+ long checkpointAddr = 0;
1249+ for (; jman.getCurrentAddress() < jman.getBlockSize() * 1.25;) {
1250+ if (jman.getCurrentAddress() - checkpointAddr > jman.getBlockSize() * 0.8) {
1251+ _persistit.checkpoint();
1252+ checkpointAddr = jman.getCurrentAddress();
1253+ }
1254+ exchange.to(count).store();
1255+ count++;
1256+ }
1257+ for (int i = 0; i < count + 100; i++) {
1258+ assertEquals(i < count, exchange.to(i).isValueDefined());
1259+ }
1260+ _persistit.close();
1261+
1262+ _persistit = new Persistit();
1263+ _persistit.initialize(config);
1264+ exchange = _persistit.getExchange(_volumeName, "RecoveryTest", false);
1265+ for (int i = 0; i < count + 100; i++) {
1266+ if (i < count && !exchange.to(i).isValueDefined()) {
1267+ System.out.println("i=" + i + " count=" + count);
1268+ break;
1269+ }
1270+ assertEquals(i < count, exchange.to(i).isValueDefined());
1271+ }
1272+
1273+ _persistit.close();
1274+ _persistit = new Persistit();
1275+ _persistit.initialize(config);
1276+ exchange = _persistit.getExchange(_volumeName, "RecoveryTest", false);
1277+ for (int i = 0; i < count + 100; i++) {
1278+ assertEquals(i < count, exchange.to(i).isValueDefined());
1279+ }
1280 }
1281
1282 private void store0() throws PersistitException {
1283
1284=== modified file 'src/test/java/com/persistit/VolumeTest.java'
1285--- src/test/java/com/persistit/VolumeTest.java 2012-08-24 13:57:19 +0000
1286+++ src/test/java/com/persistit/VolumeTest.java 2012-10-06 02:46:19 +0000
1287@@ -51,11 +51,6 @@
1288 // ok
1289 }
1290 try {
1291- assertNull(volume.getSpecification());
1292- } catch (final IllegalStateException e) {
1293- // ok
1294- }
1295- try {
1296 assertNull(volume.getStructure());
1297 } catch (final IllegalStateException e) {
1298 // ok
1299@@ -138,6 +133,7 @@
1300 @Test
1301 public void testVolumeSpecification() throws Exception {
1302 VolumeSpecification vs;
1303+ VolumeSpecification vs2;
1304
1305 vs = validVolumeSpecification("/a/b/c,name: crabcake, pageSize: 16384, initialSize: 10m, maximumSize: 100m, extensionSize: 10m, create");
1306 vs = validVolumeSpecification("/a/b/c,name:crabcake,pageSize:16384,initialSize:10m,maximumSize:100m,extensionSize:10m,create");
1307@@ -146,8 +142,11 @@
1308 assertEquals(10 * 1024 * 1024 / 16384, vs.getInitialPages());
1309 assertEquals(10 * 1024 * 1024, vs.getInitialSize());
1310 assertTrue(vs.isCreate());
1311+ assertTrue(vs.isAliased());
1312 assertFalse(vs.isCreateOnly());
1313 assertFalse(vs.isReadOnly());
1314+ vs2 = validVolumeSpecification(vs.toString());
1315+ assertEquals("Parse of toString should be equal", vs, vs2);
1316
1317 vs = validVolumeSpecification("/a/b/c");
1318 assertEquals("c", vs.getName());
1319@@ -155,16 +154,43 @@
1320 assertFalse(vs.isCreate());
1321 assertFalse(vs.isCreateOnly());
1322 assertFalse(vs.isReadOnly());
1323+ vs2 = validVolumeSpecification(vs.toString());
1324+ assertEquals("Parse of toString should be equal", vs, vs2);
1325
1326 vs = validVolumeSpecification("/a/b/c.v01");
1327 assertEquals("c", vs.getName());
1328+ vs2 = validVolumeSpecification(vs.toString());
1329+ assertEquals("Parse of toString should be equal", vs, vs2);
1330+
1331 vs = validVolumeSpecification("/a/b/c.d.v01");
1332 assertEquals("c.d", vs.getName());
1333+ vs2 = validVolumeSpecification(vs.toString());
1334+ assertEquals("Parse of toString should be equal", vs, vs2);
1335
1336 invalidVolumeSpecification("/a/b/c,name:crabcake,pagesize:16383,initialsize:10m,maximumsize:100m,extensionsize:10m,create");
1337 invalidVolumeSpecification("/a/b/c;name:crabcake,pagesize:16384,initialsize:10m,maximumsize:100m,extensionsize:10m,create");
1338 invalidVolumeSpecification("/a/b/c,name:crabcake,pagesize:16384,initialsize:10p,maximumsize:100p,extensionsize:10p,create");
1339 invalidVolumeSpecification("/a/b/c,name:crabcake,pagesize:16384,initialsize:10m,maximumsize:100m,extensionsize:10m,create,readOnly");
1340+
1341+ vs = validVolumeSpecification("/a/b/c,pageSize: 16384, initialSize: 10m, maximumSize: 100m, extensionSize: 10m, create");
1342+ assertEquals(10 * 1024 * 1024 / 16384, vs.getInitialPages());
1343+ assertEquals(10 * 1024 * 1024, vs.getInitialSize());
1344+ vs.setInitialPages(42);
1345+ vs.setExtensionPages(58);
1346+ vs.setMaximumPages(Integer.MAX_VALUE);
1347+
1348+ vs2 = validVolumeSpecification(vs.toString());
1349+ assertEquals("Parse of toString should be equal", vs, vs2);
1350+
1351+ vs.setMaximumSize(Long.MAX_VALUE);
1352+ vs.setInitialSize(Long.MAX_VALUE / 7);
1353+ vs.setExtensionSize(Long.MAX_VALUE / 23);
1354+
1355+ assertEquals(Long.MAX_VALUE / 23 / 16384, vs.getExtensionPages());
1356+
1357+ vs2 = validVolumeSpecification(vs.toString());
1358+ assertEquals("Parse of toString should be equal", vs, vs2);
1359+
1360 }
1361
1362 @Test

Subscribers

People subscribed via source and target branches