From: gu.martinm@gmail.com Date: Thu, 9 Oct 2014 15:22:47 +0000 (+0200) Subject: Data base deadlocks. How to deal with them (retries) X-Git-Url: https://git.gumartinm.name/?a=commitdiff_plain;h=ed523de8ef60c1f969521754de3b407f6d5cb16b;p=JavaForFun Data base deadlocks. How to deal with them (retries) Create deadlocks --- diff --git a/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/Main.java b/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/Main.java index 0a7c982..80ef633 100644 --- a/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/Main.java +++ b/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/Main.java @@ -8,13 +8,16 @@ import org.slf4j.LoggerFactory; import de.example.sql.deadlocks.example.FirstTransaction; import de.example.sql.deadlocks.example.SecondTransaction; +import de.example.sql.deadlocks.gate.ThreadGate; public class Main { private static final Logger logger = LoggerFactory.getLogger(Main.class); - public static void main(String[] args) { + final ThreadGate trx1Gate = new ThreadGate(); + final ThreadGate trx2Gate = new ThreadGate(); + logger.info("Starting application"); final FutureTask taskFirst = new FutureTask @@ -24,13 +27,13 @@ public class Main { @Override public void run() { final FirstTransaction first = (FirstTransaction) SpringContextLocator.getInstance().getBean("firstTransaction"); - first.doTransaction(); + first.setThreadGateTrx1(trx1Gate); + first.setThreadGateTrx2(trx2Gate); + first.doFirstStep(); } }, null ); - new Thread(taskFirst).start(); - final FutureTask taskSecond = new FutureTask ( new Runnable(){ @@ -38,11 +41,15 @@ public class Main { @Override public void run() { final SecondTransaction second = (SecondTransaction) SpringContextLocator.getInstance().getBean("secondTransaction"); - second.doTransaction(); + second.setThreadGateTrx1(trx1Gate); + second.setThreadGateTrx2(trx2Gate); + second.doSecondStep(); } }, null ); + + new Thread(taskFirst).start(); new Thread(taskSecond).start(); // Wait for end. diff --git a/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/annotation/DeadlockRetry.java b/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/annotation/DeadlockRetry.java index f18c2b9..377b2df 100644 --- a/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/annotation/DeadlockRetry.java +++ b/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/annotation/DeadlockRetry.java @@ -1,6 +1,7 @@ package de.example.sql.deadlocks.annotation; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -8,6 +9,7 @@ import java.lang.annotation.Target; @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) +@Inherited public @interface DeadlockRetry { /** diff --git a/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/annotation/GuardedBy.java b/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/annotation/GuardedBy.java new file mode 100644 index 0000000..eadfb0d --- /dev/null +++ b/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/annotation/GuardedBy.java @@ -0,0 +1,38 @@ +package de.example.sql.deadlocks.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Retention; + +/* + * Copyright (c) 2005 Brian Goetz + * Released under the Creative Commons Attribution License + * (http://creativecommons.org/licenses/by/2.5) + * Official home: http://www.jcip.net + */ + +/** + * GuardedBy + * + * The field or method to which this annotation is applied can only be accessed + * when holding a particular lock, which may be a built-in (synchronization) + * lock, or may be an explicit java.util.concurrent.Lock. + * + * The argument determines which lock guards the annotated field or method: this : + * The string literal "this" means that this field is guarded by the class in + * which it is defined. class-name.this : For inner classes, it may be necessary + * to disambiguate 'this'; the class-name.this designation allows you to specify + * which 'this' reference is intended itself : For reference fields only; the + * object to which the field refers. field-name : The lock object is referenced + * by the (instance or static) field specified by field-name. + * class-name.field-name : The lock object is reference by the static field + * specified by class-name.field-name. method-name() : The lock object is + * returned by calling the named nil-ary method. class-name.class : The Class + * object for the specified class should be used as the lock object. + */ +@Target( { ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.CLASS) +public @interface GuardedBy { + String value(); +} diff --git a/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/annotation/ThreadSafe.java b/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/annotation/ThreadSafe.java new file mode 100644 index 0000000..3d74547 --- /dev/null +++ b/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/annotation/ThreadSafe.java @@ -0,0 +1,22 @@ +package de.example.sql.deadlocks.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * ThreadSafe + * + * The class to which this annotation is applied is thread-safe. This means that + * no sequences of accesses (reads and writes to public fields, calls to public + * methods) may put the object into an invalid state, regardless of the + * interleaving of those actions by the runtime, and without requiring any + * additional synchronization or coordination on the part of the caller. + */ +@Documented +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface ThreadSafe { +} diff --git a/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/example/FirstTransaction.java b/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/example/FirstTransaction.java index 99c385c..08472ec 100644 --- a/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/example/FirstTransaction.java +++ b/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/example/FirstTransaction.java @@ -9,33 +9,88 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.transaction.annotation.Transactional; import de.example.sql.deadlocks.annotation.DeadlockRetry; +import de.example.sql.deadlocks.gate.ThreadGate; @Transactional public class FirstTransaction { private static final Logger logger = LoggerFactory.getLogger(FirstTransaction.class); private DataSource dataSource; + private ThreadGate trx2Gate; + private ThreadGate trx1Gate; + private boolean isFirstTry = true; @DeadlockRetry(maxTries = 10, interval = 5000) - public void doTransaction() { - logger.info("Running doTransaction"); + public void doFirstStep() { + if (isFirstTry) { + isFirstTry = false; + this.doFirstStepWithGate(); + } else { + // Retry after roll back. + this.doFirstStepWithoutGate(); + } + } + public void doFirstStepWithGate() { + logger.info("Start doFirstStepWithGate"); + + logger.info("doFirstStepWithGate UPDATING"); final JdbcOperations jdbcTemplate = new JdbcTemplate(dataSource); - jdbcTemplate.execute("UPDATE children SET name='Bilbo', parent_id='2' WHERE id='1'"); jdbcTemplate.execute("UPDATE parents SET name='Smith' WHERE id='1'"); + jdbcTemplate.execute("SELECT * FROM children WHERE id='1' LOCK IN SHARE MODE"); - try { - Thread.sleep(100000); - } catch (final InterruptedException e) { - logger.warn("First transaction thread interrupt"); + trx2Gate.open(); - // Restore interrupt status. - Thread.currentThread().interrupt(); - } + try { + this.trx1Gate.await(); + } catch (final InterruptedException e) { + logger.warn("interrupt error", e); + + Thread.currentThread().interrupt(); + } + this.trx1Gate.close(); + + trx2Gate.open(); + + this.doThirdStep(); - logger.info("Running endTransaction"); + logger.info("End doFirstStepWithGate"); + } + + public void doFirstStepWithoutGate() { + logger.info("Start doFirstStepWithoutGate"); + + logger.info("doFirstStepWithoutGate UPDATING"); + final JdbcOperations jdbcTemplate = new JdbcTemplate(dataSource); + jdbcTemplate.execute("UPDATE parents SET name='Smith' WHERE id='1'"); + jdbcTemplate.execute("SELECT * FROM children WHERE id='1' LOCK IN SHARE MODE"); + + this.doThirdStep(); + + logger.info("End doFirstStepWithoutGate"); + } + + + private void doThirdStep() { + logger.info("Start doThirdStep"); + + logger.info("doThirdStep UPDATING"); + final JdbcOperations jdbcTemplate = new JdbcTemplate(dataSource); + jdbcTemplate.execute("UPDATE children SET name='Bob', parent_id='1' WHERE id='2'"); + + // trx2 continues (fourth step) + + logger.info("End doThirdStep"); } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } + + public void setThreadGateTrx1(final ThreadGate trx1Gate) { + this.trx1Gate = trx1Gate; + } + + public void setThreadGateTrx2(final ThreadGate trx2Gate) { + this.trx2Gate = trx2Gate; + } } diff --git a/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/example/SecondTransaction.java b/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/example/SecondTransaction.java index 16cb126..2dd9631 100644 --- a/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/example/SecondTransaction.java +++ b/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/example/SecondTransaction.java @@ -9,24 +9,93 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.transaction.annotation.Transactional; import de.example.sql.deadlocks.annotation.DeadlockRetry; +import de.example.sql.deadlocks.gate.ThreadGate; @Transactional public class SecondTransaction { private static final Logger logger = LoggerFactory.getLogger(SecondTransaction.class); private DataSource dataSource; + private ThreadGate trx2Gate; + private ThreadGate trx1Gate; + private boolean isFirstTry = true; @DeadlockRetry(maxTries = 10, interval = 5000) - public void doTransaction() { - logger.info("Running doTransaction"); + public void doSecondStep() { + if (isFirstTry) { + isFirstTry = false; + this.doSecondStepWithGate(); + } else { + // Retry after roll back. + this.doSecondStepWithoutGate(); + } + } + + private void doSecondStepWithGate() { + logger.info("Start doSecondStepWithGate"); + + try { + this.trx2Gate.await(); + } catch (InterruptedException e) { + logger.warn("interrupt error", e); + + Thread.currentThread().interrupt(); + } + this.trx2Gate.close(); + + logger.info("doSecondStepWithGate UPDATING"); + final JdbcOperations jdbcTemplate = new JdbcTemplate(dataSource); + jdbcTemplate.execute("UPDATE parents SET name='Frodo' WHERE id='2'"); + jdbcTemplate.execute("SELECT * FROM children WHERE id='2' LOCK IN SHARE MODE"); + + // trx1 continue + trx1Gate.open(); + + try { + this.trx2Gate.await(); + } catch (InterruptedException e) { + logger.warn("interrupt error", e); + + Thread.currentThread().interrupt(); + } + this.trx2Gate.close(); + + this.doFourthStep(); + + logger.info("End doSecondStepWithGate"); + } + + private void doSecondStepWithoutGate() { + logger.info("Start doSecondStepWithoutGate"); + + logger.info("doSecondStepWithoutGate UPDATING"); + final JdbcOperations jdbcTemplate = new JdbcTemplate(dataSource); + jdbcTemplate.execute("UPDATE parents SET name='Frodo' WHERE id='2'"); + jdbcTemplate.execute("SELECT * FROM children WHERE id='2' LOCK IN SHARE MODE"); + + this.doFourthStep(); + + logger.info("End doSecondStepWithoutGate"); + } + + private void doFourthStep() { + logger.info("Start doFourthStep"); + logger.info("doFourthStep UPDATING"); final JdbcOperations jdbcTemplate = new JdbcTemplate(dataSource); - jdbcTemplate.execute("UPDATE children SET name='Frodo', parent_id='2' WHERE id='2'"); - jdbcTemplate.execute("UPDATE parents SET name='Smith' WHERE id='1'"); + jdbcTemplate.execute("UPDATE children SET name='Sauron', parent_id='2' WHERE id='1'"); - logger.info("Running endTransaction"); + logger.info("End doFourthStep"); } - public void setDataSource(DataSource dataSource) { + public void setDataSource(final DataSource dataSource) { this.dataSource = dataSource; - } + } + + public void setThreadGateTrx1(final ThreadGate trx1Gate) { + this.trx1Gate = trx1Gate; + } + + public void setThreadGateTrx2(final ThreadGate trx2Gate) { + this.trx2Gate = trx2Gate; + } } diff --git a/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/gate/ThreadGate.java b/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/gate/ThreadGate.java new file mode 100644 index 0000000..cf0ed81 --- /dev/null +++ b/SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/gate/ThreadGate.java @@ -0,0 +1,37 @@ +package de.example.sql.deadlocks.gate; + +import de.example.sql.deadlocks.annotation.GuardedBy; +import de.example.sql.deadlocks.annotation.ThreadSafe; + + + +/** + * See: §Java Concurrency in practice 14.2.6 + * @author + * + */ +@ThreadSafe +public class ThreadGate { + //CONDITION-PREDICATE: opened-since(n) (isOpen || generation>n) + @GuardedBy("this") private boolean isOpen; + @GuardedBy("this") private int generation; + + + public synchronized void close() { + isOpen = false; + } + + public synchronized void open() { + ++generation; + isOpen = true; + notifyAll(); + } + + //BLOCKS-UNTIL: opened-since(generation on entry) + public synchronized void await() throws InterruptedException { + int arrivalGeneration = generation; + while (!isOpen && arrivalGeneration == generation) + wait(); + } +} +