~akiban-technologies/akiban-persistit/trunk

« back to all changes in this revision

Viewing changes to src/test/java/com/persistit/Bug1065677Test.java

merge pbeaman: Fixes a subtle bug https://bugs.launchpad.net/akiban-persistit/+bug/1065677 and adds a unit test to demonstrate the bug.

https://code.launchpad.net/~pbeaman/akiban-persistit/fix-1065677-retrograde-timestamp-on-recovery/+merge/129547

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/**
 
2
 * Copyright © 2012 Akiban Technologies, Inc.  All rights reserved.
 
3
 * 
 
4
 * This program and the accompanying materials are made available
 
5
 * under the terms of the Eclipse Public License v1.0 which
 
6
 * accompanies this distribution, and is available at
 
7
 * http://www.eclipse.org/legal/epl-v10.html
 
8
 * 
 
9
 * This program may also be available under different license terms.
 
10
 * For more information, see www.akiban.com or contact licensing@akiban.com.
 
11
 * 
 
12
 * Contributors:
 
13
 * Akiban Technologies, Inc.
 
14
 */
 
15
 
 
16
package com.persistit;
 
17
 
 
18
import static com.persistit.unit.UnitTestProperties.VOLUME_NAME;
 
19
 
 
20
import java.io.File;
 
21
import java.io.IOException;
 
22
import java.io.RandomAccessFile;
 
23
 
 
24
import org.junit.Test;
 
25
 
 
26
import com.persistit.exception.PersistitException;
 
27
 
 
28
/**
 
29
 * https://bugs.launchpad.net/akiban-persistit/+bug/1065677
 
30
 * 
 
31
 * Doing a long delete in OLB_latest dataset, my Mac crashed, taking my VM with
 
32
 * it. The Vm was running akiban. It was via a local psql client.
 
33
 * 
 
34
 * <pre>
 
35
 * INFO 15:14:05,258 Starting services.
 
36
 * Exception in thread "main" java.lang.AssertionError
 
37
 *  at com.persistit.JournalManager$PageNode.setPrevious(JournalManager.java:1918)
 
38
 *  at com.persistit.RecoveryManager.scanLoadPageMap(RecoveryManager.java:1150)
 
39
 *  at com.persistit.RecoveryManager.scanOneRecord(RecoveryManager.java:937)
 
40
 *  at com.persistit.RecoveryManager.findAndValidateKeystone(RecoveryManager.java:784)
 
41
 *  at com.persistit.RecoveryManager.buildRecoveryPlan(RecoveryManager.java:
 
42
 * </pre>
 
43
 * 
 
44
 */
 
45
 
 
46
public class Bug1065677Test extends PersistitUnitTestCase {
 
47
 
 
48
    private final static String TREE_NAME = "Bug1065677Test";
 
49
 
 
50
    private Exchange getExchange() throws PersistitException {
 
51
        return _persistit.getExchange(VOLUME_NAME, TREE_NAME, true);
 
52
    }
 
53
 
 
54
    /**
 
55
     * This method tries to recreate the state at which the journal files in bug
 
56
     * 1065677 arrived. Plan:
 
57
     * 
 
58
     * 1. Write a transaction that updates multiple pages.
 
59
     * 
 
60
     * 2. Flush all buffers so that there are pages to recover and then crash
 
61
     * Persistit so there is no checkpoint.
 
62
     * 
 
63
     * 3. Restart Persistit, but advance the system timestamp to simulate
 
64
     * somewhat chaotic ordering of page writes created by reapplying a huge
 
65
     * delete operation in the transaction.
 
66
     * 
 
67
     * 4. Crash once again. This will leave pages from the original epoch in the
 
68
     * page map and will add versions of those pages with larger timestamps.
 
69
     * This crash simulates the JVM being killed during recovery processing. In
 
70
     * the actual case it appears there is no post-recovery checkpoint, merely
 
71
     * lots of dirty pages. To simulate this outcome we truncate about 50K of
 
72
     * bytes form the end of the journal file as a surrogate for the system
 
73
     * having not completed recovery.
 
74
     * 
 
75
     * 5. Restart the system once again. Now do some normal processing work,
 
76
     * simulated here by adding another transaction.
 
77
     * 
 
78
     * 6. Magic happens here: we now perform a rollover, which writes both the
 
79
     * branch map and the page map into a single PM record. Because the PM
 
80
     * record is written by two separate loops, some page P found in both the
 
81
     * branch map and the page map is written into two separate PM sub-records.
 
82
     * As it turns out, the version in the branch map has a smaller timestamp
 
83
     * than the one in the page map. This sets up the AssertionError.
 
84
     * 
 
85
     * 7. Restart Persistit to exploit the bug during scanLoadPageMap.
 
86
     * 
 
87
     * @throws Exception
 
88
     */
 
89
    @Test
 
90
    public void breakTimestampSequence() throws Exception {
 
91
        doTransaction();
 
92
 
 
93
        final Configuration config = _persistit.getConfiguration();
 
94
        final long lastTimestamp = _persistit.getCurrentTimestamp();
 
95
        _persistit.flush();
 
96
        _persistit.crash();
 
97
 
 
98
        _persistit = new Persistit();
 
99
        _persistit.getTimestampAllocator().updateTimestamp(lastTimestamp + 1000);
 
100
        _persistit.initialize(config);
 
101
        _persistit.crash();
 
102
        truncate();
 
103
 
 
104
        _persistit = new Persistit();
 
105
        config.setAppendOnly(true);
 
106
        _persistit.initialize(config);
 
107
 
 
108
        doTransaction();
 
109
 
 
110
        final JournalManager jman = _persistit.getJournalManager();
 
111
        jman.rollover();
 
112
        _persistit.close();
 
113
 
 
114
        _persistit = new Persistit();
 
115
        _persistit.initialize(config);
 
116
        _persistit.close();
 
117
 
 
118
    }
 
119
 
 
120
    private void doTransaction() throws Exception {
 
121
        final Exchange ex = getExchange();
 
122
        final Transaction txn = ex.getTransaction();
 
123
        txn.begin();
 
124
        ex.getValue().put(RED_FOX);
 
125
        for (int i = 0; i < 10000; i++) {
 
126
            ex.to(i).store();
 
127
        }
 
128
        txn.commit();
 
129
        txn.end();
 
130
    }
 
131
 
 
132
    private void truncate() throws IOException {
 
133
        final JournalManager jman = _persistit.getJournalManager();
 
134
        final long lastAddress = jman.getCurrentAddress();
 
135
        final File file = jman.addressToFile(lastAddress);
 
136
        final RandomAccessFile raf = new RandomAccessFile(file, "rw");
 
137
        try {
 
138
            final long length = raf.length();
 
139
            raf.setLength(length - 50000);
 
140
        } finally {
 
141
            raf.close();
 
142
        }
 
143
    }
 
144
}