Retry transactions in the face of optimistic locking exceptions for all "write" trans...
[pithos] / src / gr / ebs / gss / server / rest / TransactionHelper.java
1 /*
2  * Copyright 2009 Electronic Business Systems Ltd.
3  *
4  * This file is part of GSS.
5  *
6  * GSS is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * GSS is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with GSS.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 package gr.ebs.gss.server.rest;
20
21 import java.util.concurrent.Callable;
22 import java.util.concurrent.ExecutionException;
23 import java.util.concurrent.Executors;
24 import java.util.concurrent.ScheduledExecutorService;
25 import java.util.concurrent.ScheduledFuture;
26 import java.util.concurrent.TimeUnit;
27
28 import javax.ejb.EJBTransactionRolledbackException;
29
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32
33 /**
34  * A helper class that provides a method for repeatedly trying a transaction
35  * that is rolled back due to optimistic locking exceptions.
36  *
37  * @author  past
38  */
39 public class TransactionHelper<T> {
40         /**
41          * The logger.
42          */
43         private static Log logger = LogFactory.getLog(TransactionHelper.class);
44
45         /**
46          * Number of times to retry a transaction that was rolled back due to
47          * optimistic locking.
48          */
49         private static final int TRANSACTION_RETRIES = 10;
50
51         /**
52          * The minimum retry timeout in milliseconds.
53          */
54         private static final int MIN_TIMEOUT = 100;
55
56         /**
57          * Execute the supplied command until it completes, ignoring transaction
58          * rollbacks. Try at least TRANSACTION_RETRIES times before giving up,
59          * each time waiting a random amount of time, using an exponential
60          * backoff scheme. See http://en.wikipedia.org/wiki/Exponential_backoff
61          * for the basic idea.
62          *
63          * @param command the command to execute
64          * @return the value returned by the command
65          * @throws Exception any other exception thrown by the command
66          */
67         public T tryExecute(final Callable<T> command) throws Exception {
68                 T returnValue = null;
69                 // Schedule a Future task to call the command after delay milliseconds.
70                 int delay = 0;
71                 ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
72                 for (int i = 0; i < TRANSACTION_RETRIES; i++) {
73                         final int retry = i;
74                         ScheduledFuture<T> future = executor.schedule(new Callable<T>() {
75
76                                 @Override
77                                 public T call() throws Exception {
78                                         return command.call();
79                                 }
80                         }, delay, TimeUnit.MILLISECONDS);
81
82                         try {
83                                 returnValue = future.get();
84                                 break;
85                         } catch (ExecutionException e) {
86                                 Throwable cause = e.getCause();
87                                 if (!(cause instanceof EJBTransactionRolledbackException) ||
88                                                         retry == TRANSACTION_RETRIES - 1) {
89                                         executor.shutdownNow();
90                                         throw new Exception(cause);
91                                 }
92                                 delay = MIN_TIMEOUT + (int) (MIN_TIMEOUT * Math.random() * (i + 1));
93                                 String origCause = cause.getCause() == null ?
94                                                         cause.getClass().getName() :
95                                                         cause.getCause().getClass().getName();
96                                 logger.info("Transaction retry #" + (i+1) + " scheduled in " + delay +
97                                                         " msec due to " + origCause);
98                         }
99
100                 }
101                 executor.shutdownNow();
102                 return returnValue;
103         }
104 }