/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.geode.internal.cache;
import org.apache.logging.log4j.Logger;
import org.apache.geode.distributed.internal.InternalDistributedSystem;
import org.apache.geode.internal.logging.LogService;
/**
* TXSynchronizationThread manages beforeCompletion and afterCompletion calls on behalf of a client
* cache. The thread should be instantiated with a Runnable that invokes beforeCompletion behavior.
* Then you must invoke runSecondRunnable() with another Runnable that invokes afterCompletion
* behavior.
*
* @since GemFire 6.6
*
*/
public class TXSynchronizationRunnable implements Runnable {
private static final Logger logger = LogService.getLogger();
private Runnable firstRunnable;
private final Object firstRunnableSync = new Object();
private boolean firstRunnableCompleted;
private Runnable secondRunnable;
private final Object secondRunnableSync = new Object();
private boolean secondRunnableCompleted;
private boolean abort;
public TXSynchronizationRunnable(Runnable beforeCompletion) {
this.firstRunnable = beforeCompletion;
}
public void run() {
synchronized (this.firstRunnableSync) {
try {
this.firstRunnable.run();
} finally {
if (logger.isTraceEnabled()) {
logger.trace("beforeCompletion notification completed");
}
this.firstRunnableCompleted = true;
this.firstRunnable = null;
this.firstRunnableSync.notify();
}
}
synchronized (this.secondRunnableSync) {
// TODO there should be a transaction timeout that keeps this thread
// from sitting around forever in the event the client goes away
final boolean isTraceEnabled = logger.isTraceEnabled();
while (this.secondRunnable == null && !this.abort) {
try {
if (isTraceEnabled) {
logger.trace("waiting for afterCompletion notification");
}
this.secondRunnableSync.wait(1000);
} catch (InterruptedException e) {
// eat the interrupt and check for exit conditions
}
if (this.secondRunnable == null) {
GemFireCacheImpl cache = GemFireCacheImpl.getInstance();
if (cache == null || cache.getCancelCriterion().isCancelInProgress()) {
return;
}
}
}
if (isTraceEnabled) {
logger.trace("executing afterCompletion notification");
}
try {
if (!this.abort) {
this.secondRunnable.run();
}
} finally {
if (isTraceEnabled) {
logger.trace("afterCompletion notification completed");
}
this.secondRunnableCompleted = true;
this.secondRunnable = null;
this.secondRunnableSync.notify();
}
}
}
/**
* wait for the initial beforeCompletion step to finish
*/
public void waitForFirstExecution() {
synchronized (this.firstRunnableSync) {
while (!this.firstRunnableCompleted) {
try {
this.firstRunnableSync.wait(1000);
} catch (InterruptedException e) {
// eat the interrupt and check for exit conditions
}
// we really need the Cache Server's cancel criterion here, not the cache's
// but who knows how to get it?
GemFireCacheImpl cache = GemFireCacheImpl.getInstance();
if (cache == null) {
return;
}
cache.getCancelCriterion().checkCancelInProgress(null);
}
}
}
/**
* run the afterCompletion portion of synchronization. This method schedules execution of the
* given runnable and then waits for it to finish running
*
* @param r
*/
public void runSecondRunnable(Runnable r) {
synchronized (this.secondRunnableSync) {
this.secondRunnable = r;
this.secondRunnableSync.notify();
while (!this.secondRunnableCompleted && !this.abort) {
try {
this.secondRunnableSync.wait(1000);
} catch (InterruptedException e) {
// eat the interrupt and check for exit conditions
}
// we really need the Cache Server's cancel criterion here, not the cache's
// but who knows how to get it?
GemFireCacheImpl cache = GemFireCacheImpl.getInstance();
if (cache == null) {
return;
}
cache.getCancelCriterion().checkCancelInProgress(null);
}
}
}
/**
* stop waiting for an afterCompletion to arrive and just exit
*/
public void abort() {
synchronized (this.secondRunnableSync) {
this.abort = true;
}
}
}