You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@RunWith(YBTestRunnerNonTsanOnly.class)
public class ConcurrentDDL extends BasePgSQLTest {
private static final Logger LOG = LoggerFactory.getLogger(ConcurrentDDL.class);
@Test
public void testConcurrency() throws Exception {
AtomicBoolean stop = new AtomicBoolean(false);
AtomicBoolean errorDetected = new AtomicBoolean(false);
final int threadCount = 20;
ExecutorService exec = Executors.newFixedThreadPool(20);
for (int i = 0; i < threadCount; ++i) {
final Integer idx = i;
exec.submit(() -> {
try(Connection c = getConnectionBuilder().withTServer(idx % 3).connect();
Statement s = c.createStatement()) {
while (!stop.get()) {
helper(s, String.format("CREATE TABLE IF NOT EXISTS tt_%d( k int PRIMARY KEY, v int)", idx));
helper(s, String.format("CREATE INDEX IF NOT EXISTS tt_%d_v ON tt_%d(v)", idx, idx));
helper(s, String.format("DROP TABLE IF EXISTS tt_%d", idx));
}
} catch (Exception e) {
LOG.error("Unexpected exception", e);
errorDetected.set(true);
}
});
}
try(Connection c = getConnectionBuilder().withTServer(0).connect();
Statement s = c.createStatement()) {
final int count = 100;
for (int i = 0; i < count; ++i) {
helper(s, "CREATE TABLE IF NOT EXISTS t (a INT)");
helper(s, "DROP TABLE IF EXISTS t");
}
}
stop.set(true);
exec.shutdown();
exec.awaitTermination(1, TimeUnit.MINUTES);
assertFalse(errorDetected.get());
}
@Override
protected Map<String, String> getTServerFlags() {
Map<String, String> flagMap = super.getTServerFlags();
flagMap.put("ysql_log_statement", "all");
return flagMap;
}
private static void helper(Statement s, String query) throws SQLException {
try {
s.execute(query);
} catch(SQLException e) {
final String msg = e.getMessage();
if (!(msg.contains("Catalog Version Mismatch") || msg.contains("Try again"))) {
throw e;
}
}
}
}
fails with error
ts1|pid326976|:27271|http://127.237.244.27:12097 2021-08-10 15:46:36.693 MSK [327400] ERROR: Illegal state: Transaction for catalog table write operation 'pg_type' not found
ts1|pid326976|:27271|http://127.237.244.27:12097 2021-08-10 15:46:36.693 MSK [327400] STATEMENT: DROP TABLE IF EXISTS tt_12
2021-08-10 15:46:36,694 (pool-4-thread-13) [ERROR - org.yb.pgsql.ConcurrentDDL.lambda$testConcurrency$0(ConcurrentDDL.java:41)] Unexpected exception
org.postgresql.util.PSQLException: ERROR: Illegal state: Transaction for catalog table write operation 'pg_type' not found
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2440)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2183)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:308)
at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:441)
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:365)
at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:307)
at org.postgresql.jdbc.PgStatement.executeCachedSql(PgStatement.java:293)
at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:270)
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:266)
at org.yb.pgsql.ConcurrentDDL.helper(ConcurrentDDL.java:69)
at org.yb.pgsql.ConcurrentDDL.lambda$testConcurrency$0(ConcurrentDDL.java:38)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
The text was updated successfully, but these errors were encountered:
Summary:
DDL transaction state is controlled by the `ddl_nesting_level` counter.
When `ddl_nesting_level` is changing from 0 to 1 by the calling of the `YBIncrementDdlNestingLevel` function new DDL transaction is started. When `ddl_nesting_level` is changing from 1 to 0 by the calling of the `YBDecrementDdlNestingLevel` function current DDL transaction is committed.
In some scenarios in case of failure the `ddl_nesting_level` counter may have inappropriate value, it is required to reset them and DDL transaction state as well.
One of such scenario is the index creation procedure. By design index creation commits current DDL transaction at the middle and starts a new one immediately. For this purpose the `YBDecrementDdlNestingLevel`/`YBIncrementDdlNestingLevel` functions are called from the `DefineIndex` function. But in case current DDL is failed to commit or new DDL transaction can't be started for some reason (due to catalog version change etc) the `YBIncrementDdlNestingLevel` function may not be called or may return error. In this case `ddl_nesting_level` will have wrong value. As a result current SQL session will not be able to start/commit further DDLs at the proper time.
Solution is to reset DDL transaction state in case current DDL query finished with an error.
Note: As far as DDL transaction state is reset in case of error it is not necessary to have `success` parameter in the `YBDecrementDdlNestingLevel` function (it is called only in case of success)
Test Plan:
New unit test was introduced
```
./yb_build.sh --gtest_filter PgDDLConcurrencyTest.IndexCreation
```
Reviewers: mihnea, jason
Reviewed By: jason
Subscribers: rsami, yql
Differential Revision: https://phabricator.dev.yugabyte.com/D12609
The following test case
fails with error
The text was updated successfully, but these errors were encountered: