Merge lp:~pbeaman/akiban-persistit/fix-dynamic-volumes into lp:akiban-persistit
- fix-dynamic-volumes
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Akiban Build User | Needs Fixing | ||
Nathan Williams | Approve | ||
Review via email: mp+127848@code.launchpad.net |
Commit message
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 VolumeSpecifica
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#
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 VolumeNotFoundE
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:/
A parallel proposal to enable the useOldVSpec parameter is being proposed for Akiban Server.
Peter Beaman (pbeaman) wrote : | # |
Good ideas.
Version bumped to 3.2.0.
Modified VolumeSpecification to strengthen validity constraints initial/
Nathan Williams (nwilliams) wrote : | # |
Thanks for the tweaks. Looks good to me.
Akiban Build User (build-akiban) wrote : | # |
There were 2 failures during build/test:
* job persistit-build failed at build number 448: http://
* view must-pass failed: persistit-build is red
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
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 |
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 VolumeSpecifica tion. 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.