diff --git a/core/build.gradle b/core/build.gradle
index e9ea4c2f2..9cb919016 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -79,6 +79,9 @@ def fragileTestPatterns = [
// Changes cache timeouts and for some reason appears to have contention
// with other tests.
"google/registry/whois/WhoisCommandFactoryTest.*",
+ // Currently changes a global configuration parameter that for some reason
+ // results in timestamp inversions for other tests. TODO(mmuller): fix.
+ "google/registry/flows/host/HostInfoFlowTest.*",
] + dockerIncompatibleTestPatterns
sourceSets {
diff --git a/core/src/main/java/google/registry/model/translators/CommitLogRevisionsTranslatorFactory.java b/core/src/main/java/google/registry/model/translators/CommitLogRevisionsTranslatorFactory.java
index 266cd9f7c..e25a67fc8 100644
--- a/core/src/main/java/google/registry/model/translators/CommitLogRevisionsTranslatorFactory.java
+++ b/core/src/main/java/google/registry/model/translators/CommitLogRevisionsTranslatorFactory.java
@@ -17,13 +17,14 @@ package google.registry.model.translators;
import static com.google.common.base.MoreObjects.firstNonNull;
import static google.registry.config.RegistryConfig.getCommitLogDatastoreRetention;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
-import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
+import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Ordering;
import com.googlecode.objectify.Key;
import google.registry.model.ofy.CommitLogManifest;
+import google.registry.persistence.transaction.Transaction;
import org.joda.time.DateTime;
/**
@@ -58,12 +59,20 @@ public final class CommitLogRevisionsTranslatorFactory
*
We store a maximum of one entry per day. It will be the last transaction that happened on
* that day.
*
+ *
In serialization mode, this method just returns "revisions" without modification.
+ *
* @see google.registry.config.RegistryConfig#getCommitLogDatastoreRetention()
*/
@Override
ImmutableSortedMap> transformBeforeSave(
ImmutableSortedMap> revisions) {
- DateTime now = tm().getTransactionTime();
+
+ // Don't do anything if we're just doing object serialization.
+ if (Transaction.inSerializationMode()) {
+ return revisions;
+ }
+
+ DateTime now = ofyTm().getTransactionTime();
DateTime threshold = now.minus(getCommitLogDatastoreRetention());
DateTime preThresholdTime = firstNonNull(revisions.floorKey(threshold), START_OF_TIME);
return new ImmutableSortedMap.Builder>(Ordering.natural())
diff --git a/core/src/main/java/google/registry/model/translators/CreateAutoTimestampTranslatorFactory.java b/core/src/main/java/google/registry/model/translators/CreateAutoTimestampTranslatorFactory.java
index ff9e05eef..a7c28061e 100644
--- a/core/src/main/java/google/registry/model/translators/CreateAutoTimestampTranslatorFactory.java
+++ b/core/src/main/java/google/registry/model/translators/CreateAutoTimestampTranslatorFactory.java
@@ -15,10 +15,11 @@
package google.registry.model.translators;
import static com.google.common.base.MoreObjects.firstNonNull;
-import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
+import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import static org.joda.time.DateTimeZone.UTC;
import google.registry.model.CreateAutoTimestamp;
+import google.registry.persistence.transaction.Transaction;
import java.util.Date;
import org.joda.time.DateTime;
@@ -46,7 +47,14 @@ public class CreateAutoTimestampTranslatorFactory
/** Save a timestamp, setting it to the current time if it did not have a previous value. */
@Override
public Date saveValue(CreateAutoTimestamp pojoValue) {
- return firstNonNull(pojoValue.getTimestamp(), tm().getTransactionTime()).toDate();
- }};
+
+ // Don't do this if we're in the course of transaction serialization.
+ if (Transaction.inSerializationMode()) {
+ return pojoValue.getTimestamp() == null ? null : pojoValue.getTimestamp().toDate();
+ }
+
+ return firstNonNull(pojoValue.getTimestamp(), ofyTm().getTransactionTime()).toDate();
+ }
+ };
}
}
diff --git a/core/src/main/java/google/registry/model/translators/UpdateAutoTimestampTranslatorFactory.java b/core/src/main/java/google/registry/model/translators/UpdateAutoTimestampTranslatorFactory.java
index 21b461339..7c99c5a58 100644
--- a/core/src/main/java/google/registry/model/translators/UpdateAutoTimestampTranslatorFactory.java
+++ b/core/src/main/java/google/registry/model/translators/UpdateAutoTimestampTranslatorFactory.java
@@ -14,10 +14,11 @@
package google.registry.model.translators;
-import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
+import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import static org.joda.time.DateTimeZone.UTC;
import google.registry.model.UpdateAutoTimestamp;
+import google.registry.persistence.transaction.Transaction;
import java.util.Date;
import org.joda.time.DateTime;
@@ -46,8 +47,14 @@ public class UpdateAutoTimestampTranslatorFactory
/** Save a timestamp, setting it to the current time. */
@Override
public Date saveValue(UpdateAutoTimestamp pojoValue) {
+
+ // Don't do this if we're in the course of transaction serialization.
+ if (Transaction.inSerializationMode()) {
+ return pojoValue.getTimestamp() == null ? null : pojoValue.getTimestamp().toDate();
+ }
+
return UpdateAutoTimestamp.autoUpdateEnabled()
- ? tm().getTransactionTime().toDate()
+ ? ofyTm().getTransactionTime().toDate()
: pojoValue.getTimestamp().toDate();
}
};
diff --git a/core/src/main/java/google/registry/persistence/transaction/JpaTransactionManager.java b/core/src/main/java/google/registry/persistence/transaction/JpaTransactionManager.java
index 4e0db4e7d..ee6a60169 100644
--- a/core/src/main/java/google/registry/persistence/transaction/JpaTransactionManager.java
+++ b/core/src/main/java/google/registry/persistence/transaction/JpaTransactionManager.java
@@ -50,6 +50,11 @@ public interface JpaTransactionManager extends TransactionManager {
*/
Query query(String sqlString);
+ /**
+ * Execute the work in a transaction without recording the transaction for replay to datastore.
+ */
+ T transactWithoutBackup(Supplier work);
+
/** Executes the work in a transaction with no retries and returns the result. */
T transactNoRetry(Supplier work);
diff --git a/core/src/main/java/google/registry/persistence/transaction/JpaTransactionManagerImpl.java b/core/src/main/java/google/registry/persistence/transaction/JpaTransactionManagerImpl.java
index fa4a9a285..a998eae27 100644
--- a/core/src/main/java/google/registry/persistence/transaction/JpaTransactionManagerImpl.java
+++ b/core/src/main/java/google/registry/persistence/transaction/JpaTransactionManagerImpl.java
@@ -41,6 +41,8 @@ import google.registry.model.server.KmsSecret;
import google.registry.model.tmch.ClaimsList.ClaimsListSingleton;
import google.registry.persistence.JpaRetries;
import google.registry.persistence.VKey;
+import google.registry.schema.replay.NonReplicatedEntity;
+import google.registry.schema.replay.SqlOnlyEntity;
import google.registry.util.Clock;
import google.registry.util.Retrier;
import google.registry.util.SystemSleeper;
@@ -152,6 +154,15 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
@Override
public T transact(Supplier work) {
+ return transact(work, true);
+ }
+
+ @Override
+ public T transactWithoutBackup(Supplier work) {
+ return transact(work, false);
+ }
+
+ private T transact(Supplier work, boolean withBackup) {
return retrier.callWithRetry(
() -> {
if (inTransaction()) {
@@ -162,7 +173,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
EntityTransaction txn = txnInfo.entityManager.getTransaction();
try {
txn.begin();
- txnInfo.start(clock);
+ txnInfo.start(clock, withBackup);
T result = work.get();
txnInfo.recordTransaction();
txn.commit();
@@ -194,7 +205,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
EntityTransaction txn = txnInfo.entityManager.getTransaction();
try {
txn.begin();
- txnInfo.start(clock);
+ txnInfo.start(clock, true);
T result = work.get();
txnInfo.recordTransaction();
txn.commit();
@@ -740,11 +751,11 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
Set