Data base deadlocks. How to deal with them (retries)
authorgu.martinm@gmail.com <gu.martinm@gmail.com>
Thu, 9 Oct 2014 15:22:47 +0000 (17:22 +0200)
committergu.martinm@gmail.com <gu.martinm@gmail.com>
Thu, 9 Oct 2014 15:22:47 +0000 (17:22 +0200)
Create deadlocks

SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/Main.java
SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/annotation/DeadlockRetry.java
SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/annotation/GuardedBy.java [new file with mode: 0644]
SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/annotation/ThreadSafe.java [new file with mode: 0644]
SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/example/FirstTransaction.java
SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/example/SecondTransaction.java
SpringJava/DeadLocksSQL/src/main/java/de/example/sql/deadlocks/gate/ThreadGate.java [new file with mode: 0644]

index 0a7c982..80ef633 100644 (file)
@@ -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<Void> taskFirst = new FutureTask<Void>
@@ -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<Void> taskSecond = new FutureTask<Void>
                (
                        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.
index f18c2b9..377b2df 100644 (file)
@@ -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 (file)
index 0000000..eadfb0d
--- /dev/null
@@ -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 (file)
index 0000000..3d74547
--- /dev/null
@@ -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 {
+}
index 99c385c..08472ec 100644 (file)
@@ -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;
+       }
 }
index 16cb126..2dd9631 100644 (file)
@@ -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 (file)
index 0000000..cf0ed81
--- /dev/null
@@ -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();
+    }
+}
+