File: ErrorMessageTest.java

package info (click to toggle)
derby 10.14.2.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 79,056 kB
  • sloc: java: 691,961; sql: 42,686; xml: 20,512; sh: 3,373; sed: 96; makefile: 60
file content (244 lines) | stat: -rw-r--r-- 10,297 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
/*
 * Class org.apache.derbyTesting.functionTests.tests.lang.ErrorMessageTest
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.derbyTesting.functionTests.tests.lang;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import junit.framework.Test;
import org.apache.derbyTesting.functionTests.util.Barrier;
import org.apache.derbyTesting.junit.BaseJDBCTestCase;
import org.apache.derbyTesting.junit.BaseTestSuite;
import org.apache.derbyTesting.junit.CleanDatabaseTestSetup;
import org.apache.derbyTesting.junit.DatabasePropertyTestSetup;
import org.apache.derbyTesting.junit.JDBC;

public class ErrorMessageTest extends BaseJDBCTestCase {
    public ErrorMessageTest(String name) {
        super(name);
    }

    /**
     * Create a test suite with all the tests in this class. The tests are only
     * run in embedded mode since they test the messages generated by the
     * embedded driver.
     */
    public static Test suite() {
        BaseTestSuite suite = new BaseTestSuite("ErrorMessageTest");

        if (JDBC.vmSupportsJSR169()) {
            // Foundation 1.1 doesn't support the regex classes. Return an
            // empty test suite.
            return suite;
        }

        // Set a short wait timeout so that the expected timeout exception
        // is thrown faster.
        suite.addTest(DatabasePropertyTestSetup.setLockTimeouts(
                new ErrorMessageTest("testWaitTimeout"), 1, 2));

        // Set a short deadlock timeout so that the expected deadlock is
        // found faster. Keep the lock timeout high to prevent false lock
        // timeouts from being reported because the deadlock detector cannot
        // resolve the deadlock fast enough (DERBY-6001).
        suite.addTest(DatabasePropertyTestSetup.setLockTimeouts(
                new ErrorMessageTest("testDeadlockTimeout"), 1, 60));

        // testWaitTimeout wants more detailed error messages on timeout.
        Test test = DatabasePropertyTestSetup.singleProperty(
                suite, "derby.locks.deadlockTrace", "true");

        // create some data to work on
        return new CleanDatabaseTestSetup(test) {
            protected void decorateSQL(Statement s) throws SQLException {
                s.executeUpdate("create table t (id int primary key, " +
                                "text varchar(10))");
                s.executeUpdate("insert into t (id) values 1, 2");
            }
        };
    }

    /**
     * Test that a wait timeout prints the lock table correctly when the
     * <code>derby.locks.deadlockTrace</code> property is set. DERBY-2817
     *
     * After fix for DERBY-5564, the sql state for a lock timeout will be
     * the same whether diagnostics are on or not (ie. 40XL1).  
     */
    public void testWaitTimeout() throws SQLException {
        getConnection().setAutoCommit(false);
        Statement s = createStatement();
        assertUpdateCount(s, 1, "update t set text='xxx' where id=1");
        Connection c2 = openDefaultConnection();
        Statement s2 = c2.createStatement();
        try {
            // the first transaction has locked row with id=1, so this query
            // will time out
            JDBC.assertDrainResults(
                    s2.executeQuery("select * from t where id=1"));
            fail("Expected lock timeout");
        } catch (SQLException e) {
            assertSQLState("Not a timeout", "40XL1", e);

            // check that information about the victim is printed
            String[] msg = e.getMessage().split("\n");
            assertEquals("*** The following row is the victim ***", msg[4]);
            assertEquals("*** The above row is the victim ***", msg[6]);
            String[] victim = msg[5].split(" *\\|");
            assertTrue("Invalid XID string: " + victim[0],
                       victim[0].matches("\\d+"));
            assertEquals("Victim should be a row lock", "ROW", victim[1]);
            assertEquals("Victim should be a shared lock", "S", victim[2]);
            assertEquals("Victim should be waiting", "WAIT", victim[5]);

            // check that the rest of the lock table is dumped
            boolean locksDumped = false;
            for (int i = 7; i < msg.length - 1; i++) {
                String[] tokens = msg[i].split(" *\\|");
                assertTrue("Invalid XID string: " + tokens[0],
                           tokens[0].matches("\\d+"));
                assertTrue("Unexpected lock type: " + tokens[1],
                           tokens[1].matches("ROW|TABLE"));
                assertTrue("Unexpected lock mode: " + tokens[2],
                           tokens[2].matches("S|X|IX|IS"));
                assertEquals("Expected lock to be granted", "GRANT", tokens[5]);
                locksDumped = true;
            }
            assertTrue("No locks dumped", locksDumped);
        }
        s.close();
        s2.close();
        c2.close();
    }

    /**
     * Test that the error message from a deadlock timeout contains information
     * about the locks involved in the deadlock. DERBY-2817
     */
    public void testDeadlockTimeout()
            throws SQLException, InterruptedException {
        setAutoCommit(false);

        // Make the main transaction (T1) lock row 1 exclusively
        Statement s = createStatement();
        assertUpdateCount(s, 1, "update t set text='xxx' where id=1");

        // Start another transaction (T2) that locks row 2 exclusively
        Connection c2 = openDefaultConnection();
        c2.setAutoCommit(false);
        Statement s2 = c2.createStatement();
        assertUpdateCount(s2, 1, "update t set text='yyy' where id=2");

        // Prepare statements for T1 to lock row 2 (shared), and for T2 to
        // lock row 1 (shared).
        PreparedStatement ps1 = prepareStatement("select * from t where id=2");
        final PreparedStatement ps2 =
                c2.prepareStatement("select * from t where id=1");

        // Create a barrier for the two threads to synchronize.
        final Barrier barrier = new Barrier(2);

        final SQLException[] holder = new SQLException[2];
        final Throwable[] unexpected = new Throwable[1];
        Thread t = new Thread(new Runnable() {
                public void run() {
                    try {
                        // Let the main thread know the helper thread has
                        // started. The race for the locks can start.
                        barrier.await();

                        // This statement will be blocked because T1 holds
                        // an exclusive lock on the row we want.
                        JDBC.assertDrainResults(ps2.executeQuery());
                    } catch (SQLException e) {
                        holder[0] = e;
                    } catch (Throwable t) {
                        unexpected[0] = t;
                    }
                }
            });
        t.start();

        // Wait until the helper thread has started. Once the call returns,
        // both threads are ready, and the race for the locks can start.
        barrier.await();

        // This statement will be blocked because T2 holds an exclusive lock
        // on the row we want. So now we have T1 waiting for T2, and T2 waiting
        // for T1, and one of the transactions should be terminated because of
        // the deadlock.
        try {
            JDBC.assertDrainResults(ps1.executeQuery());
        } catch (SQLException e) {
            holder[1] = e;
        }

        // Wait for the helper thread to complete.
        t.join();

        // If the helper thread failed with something other than an
        // SQLException, report it.
        if (unexpected[0] != null) {
            fail("Helper thread failed unexpectedly", unexpected[0]);
        }

        // Check that exactly one of the threads failed, and that the failure
        // was caused by a deadlock. It is not deterministic which of the two
        // threads will be terminated.
        assertFalse("No deadlock", holder[0] == null && holder[1] == null);
        if (holder[0] != null && holder[1] != null) {
            // Both threads failed. Print some more information to the log
            // so we can see what's going on.
            printStackTrace(holder[0]);
            printStackTrace(holder[1]);
            fail("Only one of the waiters should be aborted");
        }

        SQLException deadlock = holder[0] == null ? holder[1] : holder[0];
        assertSQLState("Not a deadlock", "40001", deadlock);

        String[] lines = deadlock.getMessage().split("\n");
        assertEquals("Unexpected number of lines in message", 8, lines.length);

        Pattern[] patterns = new Pattern[] {
            Pattern.compile("Lock : ROW, T, \\(\\d+,\\d+\\)"),
            Pattern.compile(" *Waiting XID : \\{\\d+, S\\} , APP, " +
                            "select \\* from t where id=(1|2)"),
            Pattern.compile(" *Granted XID : \\{\\d+, X\\} *"),
        };

        // check the descriptions of the two locks involved in the deadlock
        for (int i = 0; i < patterns.length * 2; i++) {
            String line = lines[i+1];
            Matcher m = patterns[i%patterns.length].matcher(line);
            assertTrue("mismatch: " + line, m.matches());
        }

        s.close();
        s2.close();
        c2.rollback();
        c2.close();
    }
}