Skip to content

Instantly share code, notes, and snippets.

@nking
Created August 12, 2011 07:02
Show Gist options
  • Save nking/1141609 to your computer and use it in GitHub Desktop.
Save nking/1141609 to your computer and use it in GitHub Desktop.
Two operation updates in Google's Appengine using two, 2-phase locking transactions
The goal is to insert a new event and update the counter for it and have both
operations fail or both succeed. The solution below places each of these
2 operations is in a 2 two-phase locking transaction which must attempt to
both succeed or fail, and so the pattern of
lock1 operation1 (commit1 and lock2 operation2 commit2) or (rollback1)
is applied, with an additional pattern of first operation successfully committed
with the second retried if it fails.
private Key insert(Event event) {
PersistenceManager pmRT = null;
PersistenceManager pm = null;
Transaction txRT = null;
Transaction tx = null;
final List<Boolean> insertSucceeded = new ArrayList<Boolean>();
final List<Boolean> counterUpdateSucceeded = new ArrayList<Boolean>();
try {
pmRT = TransactionsRequiredPMF.get().getPersistenceManager();
pm = PMF.get().getPersistenceManager();
txRT = pmRT.currentTransaction();
tx = pm.currentTransaction();
txRT.setSynchronization(new javax.transaction.Synchronization() {
public void beforeCompletion() {
}
public void afterCompletion(int status) {
switch (status) {
case javax.transaction.Status.STATUS_ROLLEDBACK :
break;
case javax.transaction.Status.STATUS_COMMITTED:
log.info("insert succeeded");
insertSucceeded.add(Boolean.TRUE);
break;
}
}
});
tx.setSynchronization(new javax.transaction.Synchronization() {
public void beforeCompletion() {
}
public void afterCompletion(int status) {
switch (status) {
case javax.transaction.Status.STATUS_ROLLEDBACK :
if (!insertSucceeded.isEmpty() && insertSucceeded.get(0)) {
log.severe("insert succeeded, but update did not");
startIncrementEventAndVenueCountersTasks();
}
break;
case javax.transaction.Status.STATUS_COMMITTED:
counterUpdateSucceeded.add(Boolean.TRUE);
break;
}
}
});
event.setKey();
// this connection insists on transactional, so entities within scope
// are immediately attached. set any keys before beginning the transaction
txRT.begin();
pmRT.makePersistent(event);
FetchAiderUtility.useGetters(event);
pmRT.flush();
txRT.commit();
// if insert succeeded, update counters
if (!insertSucceeded.isEmpty() && insertSucceeded.get(0)) {
tx.begin();
log.info("increment counters");
EventCounterDAO.incrementNumberOfEntities();
VenueCounterDAO.incrementNumberOfEntities();
tx.commit();
} else {
log.info("we will not commit transaction 2");
}
} finally {
if ((txRT != null) && txRT.isActive()) {
txRT.rollback();
if ((tx != null) && tx.isActive()) {
tx.rollback();
}
}
if (pmRT != null) {
pmRT.close();
}
if (pm != null) {
pm.close();
}
}
return event.getKey();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment