public T run() {
        int currentIteration = 0;
        long waitTimeIfTimeout = 5;
        while (true) {
            Transaction txn = datastore.beginTransaction();
            try {
                return txnBlock(txn);
            } catch (ConcurrentModificationException cme) {
                if (currentIteration == retryCount) {
                    logger.warning("Failed with ConcurrentModificationException after " + retryCount + " retries: " + cme.getMessage());
                    throw cme;
                }
                logger.warning("ConcurrentModificationException caught on iteration " + currentIteration + ", retrying.");
            } catch (DatastoreTimeoutException dte) {
                if (currentIteration == retryCount) {
                    logger.warning("Failed with DatastoreTimeoutException after " + retryCount + " retries: " + dte.getMessage());
                    throw dte;
                }
                logger.warning("DatastoreTimeoutException caught on iteration " + currentIteration + ", retrying.");

                //sleep at most 10 secs, but we wait longer on successive failures
                try { Thread.sleep(Math.min(waitTimeIfTimeout, 10000)); } catch (InterruptedException ie) { /* ok */ }
                waitTimeIfTimeout *= waitTimeIfTimeout;
            } finally {
                if (txn.isActive()) {
                    txn.rollback();
                }
            }

            //if here, we failed but we're retrying
            currentIteration++;
        }
    }