src/main/java/com/googlecode/objectify/impl/TransactorNo.java
@Override
	public <R> R transactNew(final ObjectifyImpl parent, int limitTries, final Work<R> work) {
		Preconditions.checkArgument(limitTries >= 1);
		final int ORIGINAL_TRIES = limitTries;

		while (true) {
			try {
				return transactOnce(parent, work);
			} catch (DatastoreException ex) {

				// This doesn't work because the SDK considers all transactions to be non-retryable. Objectify has always
				// assumed that transactions are idempotent and retries accordingly. So we have to explicitly check against
				// code 10, which is ABORTED. https://cloud.google.com/datastore/docs/concepts/errors
//				if (!ex.isRetryable())
//					throw ex;
				// I hate this so much. Sometimes the transaction gets closed by the datastore during contention and
				// then it proceeds to freak out and 503.
				if (Code.ABORTED.getNumber() == ex.getCode() || (Code.INVALID_ARGUMENT.getNumber() == ex.getCode() && ex.getMessage().contains("transaction closed"))) {
					// Continue to retry logic
				} else {
					throw ex;
				}

				if (--limitTries > 0) {
					log.warn("Retrying {} failure for {}: {}", ex.getReason(), work, ex);
					log.trace("Details of transaction failure", ex);
					try {
						// Do increasing backoffs with randomness
						Thread.sleep(Math.min(10000, (long) (0.5 * Math.random() + 0.5) * 200 * (ORIGINAL_TRIES - limitTries + 2)));
					} catch (InterruptedException ignored) {
					}
				} else {
					throw new DatastoreException(ex.getCode(), "Failed retrying datastore " + ORIGINAL_TRIES + " times ", ex.getReason(), ex);
				}
			}
		}
	}
@Override
	public <R> R transactNew(final ObjectifyImpl parent, int limitTries, final Work<R> work) {
		Preconditions.checkArgument(limitTries >= 1);
		final int ORIGINAL_TRIES = limitTries;

		while (true) {
			try {
				return transactOnce(parent, work);
			} catch (DatastoreException ex) {

				// This doesn't work because the SDK considers all transactions to be non-retryable. Objectify has always
				// assumed that transactions are idempotent and retries accordingly. So we have to explicitly check against
				// code 10, which is ABORTED. https://cloud.google.com/datastore/docs/concepts/errors
//				if (!ex.isRetryable())
//					throw ex;
				// I hate this so much. Sometimes the transaction gets closed by the datastore during contention and
				// then it proceeds to freak out and 503.
				if (Code.ABORTED.getNumber() == ex.getCode() || (Code.INVALID_ARGUMENT.getNumber() == ex.getCode() && ex.getMessage().contains("transaction closed"))) {
					// Continue to retry logic
				} else {
					throw ex;
				}

				if (--limitTries > 0) {
					log.warn("Retrying {} failure for {}: {}", ex.getReason(), work, ex);
					log.trace("Details of transaction failure", ex);
					try {
						// Do increasing backoffs with randomness
						Thread.sleep(Math.min(10000, (long) (0.5 * Math.random() + 0.5) * 200 * (ORIGINAL_TRIES - limitTries + 2)));
					} catch (InterruptedException ignored) {
					}
				} else {
					throw new DatastoreException(ex.getCode(), "Failed retrying datastore " + ORIGINAL_TRIES + " times ", ex.getReason(), ex);
				}
			}
		}
	}
/* (non-Javadoc)
@see com.googlecode.objectify.impl.Transactor#transactNew(com.googlecode.objectify.impl.ObjectifyImpl, int, com.googlecode.objectify.Work)