Hello!
I’m running an AppEngine Standard locally (DevAppServer)
Per the following documentation, it should be possible to push a task to TaskQueue inside a Datastore transaction:
https://cloud.google.com/appengine/docs/legacy/standard/java/taskqueue/push/creating-tasks#enqueuing_tasks_in_cloud_datastore_transactions
And indeed, if I run similar code like that in the snippet from the documentation inside an appengine request, it is executed without an error.
I’d like to have a similar functionality, but instead in App Engine request I’d like to run it as a standalone java utility. The following is a test snippet of what I want to achieve.
import com.google.appengine.api.datastore.*; import com.google.appengine.api.taskqueue.*; import com.google.appengine.tools.remoteapi.*; import java.util.UUID; public class Utility { public static void main(String[] args) throws Exception { try (var ignoredApiRemote = openRemoteApi(createLocalhostOptions())) { DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); Transaction txn = datastore.beginTransaction(TransactionOptions.Builder.withXG(true)); try { Entity entity = createTestEntity(); datastore.put(txn, entity); QueueFactory.getDefaultQueue().add(txn, TaskOptions.Builder.withDefaults().url("/queue").param("command", "test-command")); txn.commit(); } finally { if (txn.isActive()) { txn.rollback(); } } } } public static Entity createTestEntity() { Entity entity = new Entity("Account"); entity.setProperty("id", UUID.randomUUID().toString()); entity.setProperty("name", "John Doe"); return entity; } public static AutoCloseable openRemoteApi(RemoteApiOptions options) throws Exception { var installer = new RemoteApiInstaller(); installer.install(options); return installer::uninstall; } public static RemoteApiOptions createLocalhostOptions() { return new RemoteApiOptions().server("localhost", 8080) .remoteApiPath("/remote") .useDevelopmentServerCredential(); } }
To simulate the appengine request context I’m using RemoteApiInstaller.
If I call Queue.add with txn parameter it throws the following exception:
Exception in thread “main” com.google.appengine.api.taskqueue.TransactionalTaskException
at com.google.appengine.api.taskqueue.QueueApiHelper.translateError(QueueApiHelper.java:130)
at com.google.appengine.api.taskqueue.QueueApiHelper.translateError(QueueApiHelper.java:199)
at com.google.appengine.api.taskqueue.QueueApiHelper$1.convertException(QueueApiHelper.java:70)
at com.google.appengine.api.utils.FutureWrapper.get(FutureWrapper.java:123)
at com.google.appengine.api.utils.FutureWrapper.get(FutureWrapper.java:107)
at com.google.appengine.api.utils.FutureWrapper.get(FutureWrapper.java:107)
at com.google.appengine.api.taskqueue.QueueApiHelper.getInternal(QueueApiHelper.java:101)
at com.google.appengine.api.taskqueue.QueueImpl.add(QueueImpl.java:480)
at com.mobisystems.connect.utilities.Bla.main(Bla.java:23)
Caused by: java.lang.IllegalArgumentException: transaction has expired or is invalid
at com.google.appengine.api.datastore.DatastoreApiHelper.translateError(DatastoreApiHelper.java:67)
at com.google.appengine.api.taskqueue.QueueApiHelper.translateError(QueueApiHelper.java:132)
… 8 more
If I call Queue.add with null for transaction parameter it succeeds.
How can I achieve the transactional behavior of adding to taskQueue only if the transaction succeeds?
N.B. The following snippet doesn’t have to be executed against localhost devappserver. I tried with existing remote project / appengine and received the same error.