Simple database-based instance synchronization - java

I'm working on a service that runs in a java app server farm that needs to do some periodic jobs (say, once every 2 minutes). The service must interface to external entities, and it is necessary to synchronize the different instances so that only one of them works on the job at a given time. Since the service uses a DB during this job, I thought of implementing the synchronization based on a simple DB table:
id, owner, stamp
where id is the lock's id, owner is the current owner and stamp is the time it was locked.
The methods would be:
tryLock(id, maxAge, owner) - to try to lock a record or break an old record
refresh(id, owner) - to update the stamp to signal we're still around working on the job
release(id, owner) - to release the lock
How would you implement this?
Edit: removed my implementation, I'll post it as an "answer"

I came up with the following implementation, but I'm not sure if it handles all corner cases (and I'm not entirely sure I'm using the BeanManagedTransaction correctly). Also, if you think this syncronization problem could be handled in a simpler way, point me to the right direction.
#Service(objectName=Sync.EjbName)
#Management(SyncMgt.class)
#TransactionManagement(value=TransactionManagementType.BEAN)
public class SyncSvc implements SyncMgt {
#PersistenceContext
protected EntityManager entityManager_;
#Resource
protected UserTransaction utx_;
#TransactionAttribute(TransactionAttributeType.REQUIRED)
private boolean update(SyncRecord sr, String owner) {
Date stamp = (owner != null) ? new Date() : null;
Query q;
if (sr.getOwner() != null) {
q = entityManager_.createQuery("UPDATE SyncRecord sr SET sr.owner = :newOwner, sr.stamp = :stamp WHERE sr.id = :id AND sr.owner = :origOwner AND sr.stamp = :origStamp");
q.setParameter("origOwner", sr.getOwner());
q.setParameter("origStamp", sr.getStamp()); // make it fail if someone refreshed in the meantime
}
else {
q = entityManager_.createQuery("UPDATE SyncRecord sr SET sr.owner = :newOwner, sr.stamp = :stamp WHERE sr.id = :id AND sr.owner IS NULL");
}
q.setParameter("id", sr.getId());
q.setParameter("newOwner", owner);
q.setParameter("stamp", stamp);
int res = q.executeUpdate();
if (res != 1) {
return false;
}
return true;
}
#TransactionAttribute(TransactionAttributeType.REQUIRED)
private boolean tryLockImpl(String id, long maxAge, String owner) {
SyncRecord sr = entityManager_.find(SyncRecord.class, id);
if (sr == null) {
// no record yet, create one
sr = new SyncRecord(id, owner);
sr.touch();
entityManager_.persist(sr);
entityManager_.flush();
return true;
}
// found a SyncRecord, let's see who owns it
if (owner.equals(sr.getOwner())) {
// log some warning, re-locking old lock, should use refresh instead
return update(sr, owner);
}
if (sr.getOwner() == null) {
// sr is not held by anyone, safe to grab it
return update(sr, owner);
}
// someone else holds it, let's check the age
if (maxAge >= 0) {
long maxAgeStamp = System.currentTimeMillis() - maxAge;
if (sr.getStamp().getTime() < maxAgeStamp) {
if (update(sr, owner)) {
return true;
}
return false;
}
}
return false;
}
// Sync impl:
/**
* Try to lock "id" for "owner"
* If the lock is held by someone else, but is older than maxAge, break it
*/
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public boolean tryLock(String id, long maxAge, String owner) {
if (id == null)
throw new IllegalArgumentException("id is null");
try {
utx_.begin();
if (tryLockImpl(id, maxAge, owner)) {
utx_.commit();
return true;
}
}
catch (EntityExistsException e) {
// failed to lock, someone beat us to it
}
catch (Throwable e) {
// some fishy error, raise alarm, log, etc
}
try {
utx_.rollback();
}
catch (Throwable e) {
// log the error, not much else we can do at this point
}
return false;
}
/**
* Refresh lock "id" belonging to "owner" (update its stamp)
*/
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public boolean refresh(String id, String owner) {
if (id == null)
throw new IllegalArgumentException("id is null");
try {
utx_.begin();
SyncRecord sr = entityManager_.find(SyncRecord.class, id);
if (sr == null || !owner.equals(sr.getOwner())) {
utx_.rollback();
return false;
}
if (update(sr, owner)) {
utx_.commit();
return true;
}
}
catch (Throwable e) {
// some fishy error, raise alarm, log, etc
}
try {
utx_.rollback();
}
catch (Throwable e) {
// log the error, not much else we can do at this point
}
return false;
}
/**
* release lock "id" held by "owner"
*/
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void release(String id, String owner) {
if (id == null)
throw new IllegalArgumentException("id is null");
try {
utx_.begin();
SyncRecord sr = entityManager_.find(SyncRecord.class, id);
if (sr == null || !owner.equals(sr.getOwner())) {
// we don't own it
utx_.rollback();
return;
}
if (update(sr, null)) {
utx_.commit();
return;
}
}
catch (Throwable e) {
// some fishy error, raise alarm, log, etc
}
try {
utx_.rollback();
}
catch (Throwable e) {
// log the error, not much else we can do at this point
}
}
// LifeCycle impl:
public void start() {}
public void stop() {}
}

Related

Unable to find DistributedTask or any alternative in hazelcast 4.x or greater

Use case: We use hazelcast’s DurableExecutorService for distributed task execution. Before submitting a task, we wrap it into a custom future task. That we do to set context, get timestamps and for rollback on cancellation of task. Please find sample template below:
public class CustomFuture<V> extends FutureTask<V> {
private SomeCallableCommand<V> command;
private Context conext;
private Timestamp scheduledTimestamp, startTimestamp, finishTimestamp;
public CustomFuture(SomeCallableCommand<V> command) {
super(command);
this.command = command;
this.conext = conextHolder.getContext().copy();
scheduledTimestamp = new Timestamp(System.currentTimeMillis());
}
#Override
public void run() {
try {
startTimestamp = new Timestamp(System.currentTimeMillis());
setContext();
super.run();
} finally {
removeContext();
}
}
#Override
protected void done() {
super.done();
finishTimestamp = new Timestamp(System.currentTimeMillis());
}
#Override
public oolean cancel(oolean mayInterruptIfRunning) {
oolean cancelled = super.cancel(mayInterruptIfRunning);
if (cancelled) {
try {
command.rollback();
} catch (Exception e) {
_logger.error(“Unable to rollback command”, e); //$NON-NLS-1$
}
finishTimestamp = new Timestamp(System.currentTimeMillis());
}
return cancelled;
}
public String getExecutionStatus() {
if (finishTimestamp != null) {
if (isCancelled()) {
return “cancelled”; //$NON-NLS-1$
} else {
return “finished”; //$NON-NLS-1$
}
} else if (startTimestamp != null) {
return “executing”; //$NON-NLS-1$
}
return “scheduled”; //$NON-NLS-1$
}
public oolean isExecuting() {
return (startTimestamp != null && finishTimestamp == null);
}
public String getDescription() {
return getCommand().getDescription();
}
/**
* Duration, in milliseconds, between the time the task is scheduled and the time it starts.
* If there are no available threads in the pool, this is roughly the time a task spends in the executor queue.
* #return
*/
public long getIdleTimeMillis() {
if (scheduledTimestamp == null) {
return -1;
}
// Take difference from current time if task never executed
startTimestamp = (startTimestamp == null) ? new Timestamp(System.currentTimeMillis()) : startTimestamp;
return startTimestamp.getTime() – scheduledTimestamp.getTime();
}
}
Submitting to executor service:
// Wrapping up callable into CustomFuture and submitting it to durable executor service.
CustomFuture task = new CustomFuture(callableCommand);
executor.execute(task);
Problem: Executor service throws serialization exception, as FutureTask(extended by CustomTask) is not serializable.
Please let us know if there’s any alternative DistributedTask or some other FutureTask implementation to solve this problem.

How to limit the number of records while reading from mysql table using multithreading

I have 1.5 million records in my mysql table. I'm trying to read all the records in a batch process i.e,planning to read 1000 records in a batch and print those records in console.
For this I'm planning to implement multithreading concept using java. How can I implement this?
In MySQL you get all records at once or you get them one by one in a streaming fashion (see this answer). Alternatively, you can use the limit keyword for chunking (see this answer).
Whether you use streaming results or chunking, you can use multi-threading to process (or print) data while you read data. This is typically done using a producer-consumer pattern where, in this case, the producer retrieves data from the database, puts it on a queue and the consumer takes the data from the queue and processes it (e.g. print to the console).
There is a bit of administration overhead though: both producer and consumer can freeze or trip over an error and both need to be aware of this so that they do not hang forever (potentially freezing your application). This is where "reasonable" timeouts come in ("reasonable" depends entirely on what is appropriate in your situation).
I have tried to put this in a minimal running example, but it is still a lot of code (see below). There are two commented lines that can be used to test the timeout-case. There is also a refreshTestData variable that can be used to re-use inserted records (inserting records can take a long time).
To keep it clean, a lot of keywords like private/public are omitted (i.e. these need to be added in non-demo code).
import java.sql.*;
import java.util.*;
import java.util.concurrent.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FetchRows {
private static final Logger log = LoggerFactory.getLogger(FetchRows.class);
public static void main(String[] args) {
try {
new FetchRows().print();
} catch (Exception e) {
e.printStackTrace();
}
}
void print() throws Exception {
Class.forName("com.mysql.jdbc.Driver").newInstance();
Properties dbProps = new Properties();
dbProps.setProperty("user", "test");
dbProps.setProperty("password", "test");
try (Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", dbProps)) {
try (Statement st = conn.createStatement()) {
prepareTestData(st);
}
// https://stackoverflow.com/a/2448019/3080094
try (Statement st = conn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY,
java.sql.ResultSet.CONCUR_READ_ONLY)) {
st.setFetchSize(Integer.MIN_VALUE);
fetchAndPrintTestData(st);
}
}
}
boolean refreshTestData = true;
int maxRecords = 5_555;
void prepareTestData(Statement st) throws SQLException {
int recordCount = 0;
if (refreshTestData) {
st.execute("drop table if exists fetchrecords");
st.execute("create table fetchrecords (id mediumint not null auto_increment primary key, created timestamp default current_timestamp)");
for (int i = 0; i < maxRecords; i++) {
st.addBatch("insert into fetchrecords () values ()");
if (i % 500 == 0) {
st.executeBatch();
log.debug("{} records available.", i);
}
}
st.executeBatch();
recordCount = maxRecords;
} else {
try (ResultSet rs = st.executeQuery("select count(*) from fetchrecords")) {
rs.next();
recordCount = rs.getInt(1);
}
}
log.info("{} records available for testing.", recordCount);
}
int batchSize = 1_000;
int maxBatchesInMem = 3;
int printFinishTimeoutS = 5;
void fetchAndPrintTestData(Statement st) throws SQLException, InterruptedException {
final BlockingQueue<List<FetchRecordBean>> printQueue = new LinkedBlockingQueue<List<FetchRecordBean>>(maxBatchesInMem);
final PrintToConsole printTask = new PrintToConsole(printQueue);
new Thread(printTask).start();
try (ResultSet rs = st.executeQuery("select * from fetchrecords")) {
List<FetchRecordBean> l = new LinkedList<>();
while (rs.next()) {
FetchRecordBean bean = new FetchRecordBean();
bean.setId(rs.getInt("id"));
bean.setCreated(new java.util.Date(rs.getTimestamp("created").getTime()));
l.add(bean);
if (l.size() % batchSize == 0) {
/*
* The printTask can stop itself when this producer is too slow to put records on the print-queue.
* Therefor, also check printTask.isStopping() to break the while-loop.
*/
if (printTask.isStopping()) {
throw new TimeoutException("Print task has stopped.");
}
enqueue(printQueue, l);
l = new LinkedList<>();
}
}
if (l.size() > 0) {
enqueue(printQueue, l);
}
} catch (TimeoutException | InterruptedException e) {
log.error("Unable to finish printing records to console: {}", e.getMessage());
printTask.stop();
} finally {
log.info("Reading records finished.");
if (!printTask.isStopping()) {
try {
enqueue(printQueue, Collections.<FetchRecordBean> emptyList());
} catch (Exception e) {
log.error("Unable to signal last record to print.", e);
printTask.stop();
}
}
if (!printTask.await(printFinishTimeoutS, TimeUnit.SECONDS)) {
log.error("Print to console task did not finish.");
}
}
}
int enqueueTimeoutS = 5;
// To test a slow printer, see also Thread.sleep statement in PrintToConsole.print.
// int enqueueTimeoutS = 1;
void enqueue(BlockingQueue<List<FetchRecordBean>> printQueue, List<FetchRecordBean> l) throws InterruptedException, TimeoutException {
log.debug("Adding {} records to print-queue.", l.size());
if (!printQueue.offer(l, enqueueTimeoutS, TimeUnit.SECONDS)) {
throw new TimeoutException("Unable to put print data on queue within " + enqueueTimeoutS + " seconds.");
}
}
int dequeueTimeoutS = 5;
class PrintToConsole implements Runnable {
private final BlockingQueue<List<FetchRecordBean>> q;
private final CountDownLatch finishedLock = new CountDownLatch(1);
private volatile boolean stop;
public PrintToConsole(BlockingQueue<List<FetchRecordBean>> q) {
this.q = q;
}
#Override
public void run() {
try {
while (!stop) {
List<FetchRecordBean> l = q.poll(dequeueTimeoutS, TimeUnit.SECONDS);
if (l == null) {
log.error("Unable to get print data from queue within {} seconds.", dequeueTimeoutS);
break;
}
if (l.isEmpty()) {
break;
}
print(l);
}
if (stop) {
log.error("Printing to console was stopped.");
}
} catch (Exception e) {
log.error("Unable to print records to console.", e);
} finally {
if (!stop) {
stop = true;
log.info("Printing to console finished.");
}
finishedLock.countDown();
}
}
void print(List<FetchRecordBean> l) {
log.info("Got list with {} records from print-queue.", l.size());
// To test a slow printer, see also enqueueTimeoutS.
// try { Thread.sleep(1500L); } catch (Exception ignored) {}
}
public void stop() {
stop = true;
}
public boolean isStopping() {
return stop;
}
public void await() throws InterruptedException {
finishedLock.await();
}
public boolean await(long timeout, TimeUnit tunit) throws InterruptedException {
return finishedLock.await(timeout, tunit);
}
}
class FetchRecordBean {
private int id;
private java.util.Date created;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public java.util.Date getCreated() {
return created;
}
public void setCreated(java.util.Date created) {
this.created = created;
}
}
}
Dependencies:
mysql:mysql-connector-java:5.1.38
org.slf4j:slf4j-api:1.7.20 (and to get logging shown in console: ch.qos.logback:logback-classic:1.1.7 with ch.qos.logback:logback-core:1.1.7)

Couchbase: Initialization from within static code block takes longer

I put my couchbase initialization code inside a static code block:
static {
initCluster();
bucket = initBucket("graph");
metaBucket = initBucket("meta");
BLACKLIST = new SetObservingCache<String>(() -> getBlackList(), BLACKLIST_REFRESH_INTERVAL_SEC * 1000);
}
I know it's not a good practice but it was very convenient and served its purpose, as I need this code to run exactly once in a multi-threaded environment and block all subsequent calls from other threads until it's finished (blacklist has been initialized).
To my surprise, the call to getBlacklist() timed out and couldn't be completed.
However, when calling it again after 2 minutes (that's what the ObservingCache does), it completed in less than a second.
In order to solve this, I refactored my code and made the blacklist acquisition lazy:
public boolean isBlacklisted(String key) {
// BLACKLIST variable should NEVER be touched outside of this context.
assureBlacklistIsPopulated();
return BLACKLIST != null ? BLACKLIST.getItems().contains(key) : false;
}
private void assureBlacklistIsPopulated() {
if (!ENABLE_BLACKLIST) {
return;
}
if (BLACKLIST == null) {
synchronized (CouchConnectionManager.class) {
if (BLACKLIST == null) {
BLACKLIST = new SetObservingCache<String>(() -> getBlackList(), BLACKLIST_REFRESH_INTERVAL_SEC * 1000);
}
}
}
}
The call to isBlacklisted() blocks all other threads that attempt to check if an entry is blacklisted until blacklist is initialized.
I'm not a big fan of this solution because it's very verbose and error prone - one might try to read from BLACKLIST without calling assureBlacklistIsPopulated() beforehand.
The static (and non final) fields within the class are as follows:
private static CouchbaseCluster cluster;
private static Bucket bucket;
private static Bucket metaBucket;
private static SetObservingCache<String> BLACKLIST;
I can't figure out why the call succeeded when it wasn't a part of the static initialization block. Is there any known performance-related vulnerability of the static initialization block that I'm not aware of?
EDIT: Added initialization code per request
private Bucket initBucket(String bucketName) {
while(true) {
Throwable t = null;
try {
ReportableThread.updateStatus("Initializing bucket " + bucketName);
return cluster.openBucket(bucketName);
} catch(Throwable t1) {
t1.printStackTrace();
t = t1;
}
try {
ReportableThread.updateStatus(String.format("Failed to open bucket: %s reason: %s", bucketName, t));
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void initCluster() {
CouchbaseEnvironment env = DefaultCouchbaseEnvironment
.builder()
.kvTimeout(MINUTE)
.connectTimeout(MINUTE)
.retryStrategy(FailFastRetryStrategy.INSTANCE)
.requestBufferSize(16384 * 2)
.responseBufferSize(16384 * 2)
.build();
while(true) {
ReportableThread.updateStatus("Initializing couchbase cluster");
Throwable t = null;
try {
cluster = CouchbaseCluster.create(env, getServerNodes());
if(cluster != null) {
return;
}
} catch(Throwable t1) {
t1.printStackTrace();
t = t1;
}
try {
ReportableThread.updateStatus(String.format("Failed to create connection to couch %s", t));
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public Set<String> getBlackList() {
ReportableThread.updateStatus("Getting black list");
AbstractDocument<?> abstractDoc = get("blacklist", metaBucket, JsonArrayDocument.class);
JsonArrayDocument doc = null;
if (abstractDoc != null && abstractDoc instanceof JsonArrayDocument) {
doc = (JsonArrayDocument)abstractDoc;
} else {
return new HashSet<String>();
}
ReportableThread.updateStatus(String.format("%s: Got %d items | sorting items", new Date(System.currentTimeMillis()).toString(), doc.content().size()));
HashSet<String> ret = new HashSet<String>();
for (Object string : doc.content()) {
if (string != null) {
ret.add(string.toString());
}
}
return ret;
}
1st: you are doing the double-check idiom. That's always bad.
Put only one if(BLACKLIST==null) and it must be inside the synchronized.
2nd: the lazy init is fine, but do it in a static getInstance() and NEVER expose the BLACKLIST field.

login in flex and java using multithreading

I have problem with my login application in java and flex. we use fingerprint login. the system waits for 60 seconds for any fingerprint input from the user. After that it automatically goes out of the page. The user also has text password option on that page. When user clicks on that option, control goes to some other page. But the problem is whenver user click on text password option, he is redirected but the thread of 60 seconds keep running. Can any one help me how to stop that thread. Here is my code. I am using blocking queue concept to get out of the input screen by inputting some dummy value of one bit.
private void interruptCaptureProcess() {
System.out.println("Interrupting Capture Process.");
ExactScheduledRunnable fingerScanInterruptThread = new ExactScheduledRunnable()
{
public void run()
{
try
{
if (capture != null)
{
DPFPSampleFactoryImpl test = new DPFPSampleFactoryImpl();
samples.put(test.createSample(new byte[1]));
capture.stopCapture();
}
}
catch (Exception e)
{
LOGGER.error("interruptCaptureProcess", e);
e.printStackTrace();
}
}
};
timeOutScheduler.schedule(fingerScanInterruptThread, getTimeOutValue(), TimeUnit.SECONDS);
}
/**
* Scans and Verifies the user finger print by matching it with the previous registered template for the user.
*
* #param userVO is the user value object which has to be verified.
* #return the acknowledgment string according to result for operation performed.
* #throws UserServiceException when there is an error in case of getting user record.
*/
public String verifyUserFingerPrint(Long userId) throws LoginServiceException {
System.out.println("Performing fingerprint verification...\n");
interruptCaptureProcess();
UserVO userVO = null;
try {
userVO = new UserService().findUserById(userId, true);
if (userVO != null) {
stopCaptureProcess();
DPFPSample sample = getSample(selectReader(), "Scan your finger\n");
timeOutScheduler.shutdownNow();
if (sample.serialize().length == 1) {
System.out.println("Coming in code");
return null;
} else if (sample.serialize().length == 2) {
System.out.println("Capturing Process has been Timed-Out");
return TIMEOUT;
}
if (sample == null)
throw new UserServiceException("Error in scanning finger");
DPFPFeatureExtraction featureExtractor = DPFPGlobal.getFeatureExtractionFactory()
.createFeatureExtraction();
DPFPFeatureSet featureSet = featureExtractor.createFeatureSet(sample,
DPFPDataPurpose.DATA_PURPOSE_VERIFICATION);
DPFPVerification matcher = DPFPGlobal.getVerificationFactory().createVerification();
matcher.setFARRequested(DPFPVerification.MEDIUM_SECURITY_FAR);
byte[] tempByte = userVO.getFingerPrint();
DPFPTemplateFactory facotory = new DPFPTemplateFactoryImpl();
for (DPFPFingerIndex finger : DPFPFingerIndex.values()) {
DPFPTemplate template = facotory.createTemplate(tempByte);
if (template != null) {
DPFPVerificationResult result = matcher.verify(featureSet, template);
// Fix of enh#1029
Map<ScriptRxConfigType, Map<ScriptRxConfigName, String>> scriptRxConfigMap = ScriptRxConfigMapSingleton
.getInstance().getScriptRxConfigMap();
Map<ScriptRxConfigName, String> fingerPrintPropertiesMap = scriptRxConfigMap
.get(ScriptRxConfigType.FINGERPRINT);
String fingerPrintDemoMode = fingerPrintPropertiesMap.get(ScriptRxConfigName.DEMOMODE);
if (fingerPrintDemoMode != null && fingerPrintDemoMode.equalsIgnoreCase("DemoEnabled")) {
return "LOGS_MSG_101";
}
// End of fix of enh#1029
if (result.isVerified()) {
System.out.println("Matching finger: %s, FAR achieved: %g.\n" + fingerName(finger)
+ (double) result.getFalseAcceptRate() / DPFPVerification.PROBABILITY_ONE);
return "LOGS_MSG_101";
}
}
}
}
} catch (IndexOutOfBoundsException iob) {
LOGGER.error("verifyUserFingerPrint", iob);
throw new LoginServiceException("LOGS_ERR_101", iob);
} catch (Exception exp) {
LOGGER.error("verifyUserFingerPrint", exp);
System.out.println("Failed to perform verification.");
throw new LoginServiceException("LOGS_ERR_105", exp);
} catch (Throwable th) {
LOGGER.error("verifyUserFingerPrint", th);
throw new LoginServiceException("LOGS_ERR_106", th.getMessage(), th);
}
System.out.println("No matching fingers found for \"%s\".\n" + userVO.getFirstName().toUpperCase());
throw new LoginServiceException("LOGS_ERR_107", null);
}
/* finger scanning process
*/
private void stopCaptureProcess() {
ExactScheduledRunnable fingerScanInterruptThread = new ExactScheduledRunnable() {
public void run() {
try {
DPFPSampleFactoryImpl test = new DPFPSampleFactoryImpl();
samples.put(test.createSample(new byte[2]));
capture.stopCapture();
} catch (Throwable ex) {
ex.printStackTrace();
}
}
};
timeOutScheduler.schedule(fingerScanInterruptThread, getTimeOutValue(), TimeUnit.SECONDS);
}
/**
* API will get the value for the finger scanner time out configuration(Default will be 60 seconds)
*/
private long getTimeOutValue() {
long waitTime = 60;
String configValue = ScriptRxSingleton.getInstance().getConfigurationValue(ConfigType.Security,
ConfigName.FingerprintTimeout);
try {
waitTime = Long.valueOf(configValue);
} catch (NumberFormatException e) {
LOGGER.debug("Configuration value is not a number for FingerTimeOut", e);
}
return waitTime;
}
Stopping blocking tasks in Java is a complicated topic, and requires cooperation between the blocking code and the code that wants to unblock it. The most common way in Java is to interrupt the thread that is blocking, which works if the code that is blocking and the code around it understands interruption. If that's not the case you're out of luck. Here's an answer that explains one way to interrupt a thread that is blocking in an Executor: https://stackoverflow.com/a/9281038/1109

How to make a thread try to reconnect to the Database x times using JDBCTemplate

I have a single thread trying to connect to a database using JDBCTemplate as follows:
JDBCTemplate jdbcTemplate = new JdbcTemplate(dataSource);
try{
jdbcTemplate.execute(new CallableStatementCreator() {
#Override
public CallableStatement createCallableStatement(Connection con)
throws SQLException {
return con.prepareCall(query);
}
}, new CallableStatementCallback() {
#Override
public Object doInCallableStatement(CallableStatement cs)
throws SQLException {
cs.setString(1, subscriberID);
cs.execute();
return null;
}
});
} catch (DataAccessException dae) {
throw new CougarFrameworkException(
"Problem removing subscriber from events queue: "
+ subscriberID, dae);
}
I want to make sure that if the above code throws DataAccessException or SQLException, the thread waits a few seconds and tries to re-connect, say 5 more times and then gives up. How can I achieve this? Also, if during execution the database goes down and comes up again, how can i ensure that my program recovers from this and continues running instead of throwing an exception and exiting?
Thanks in advance.
Try this. My considerations are : run a loop until the statements executed successfully. If there is a failure, tolerate the failure for 5 times and each time it will wait for 2 seconds for the next execution.
JDBCTemplate jdbcTemplate = new JdbcTemplate(dataSource);
boolean successfullyExecuted = false;
int failCount = 0;
while (!successfullyExecuted){
try{
jdbcTemplate.execute(new CallableStatementCreator() {
#Override
public CallableStatement createCallableStatement(Connection con)
throws SQLException {
return con.prepareCall(query);
}
}, new CallableStatementCallback() {
#Override
public Object doInCallableStatement(CallableStatement cs)
throws SQLException {
cs.setString(1, subscriberID);
cs.execute();
return null;
}
});
successfullyExecuted = true;
} catch (DataAccessException dae) {
if (failedCount < 5){
failedCount ++;
try{java.lang.Thread.sleep(2 * 1000L); // Wait for 2 seconds
}catch(java.lang.Exception e){}
}else{
throw new CougarFrameworkException(
"Problem removing subscriber from events queue: "
+ subscriberID, dae);
}
} catch (java.sql.SQLException sqle){
if (failedCount < 5){
failedCount ++;
}else{
try{java.lang.Thread.sleep(2 * 1000L); // Wait for 2 seconds
}catch(java.lang.Exception e){}
throw new CougarFrameworkException(
"Problem removing subscriber from events queue: "
+ subscriberID, dae);
}
}
}
It might be worthwhile for you to look into Spring's Aspect support. What you're describing is retry with (constant) backoff, and chances are you'll eventually need it somewhere else, be it talking to a web service, an email server, or any other complicated system susceptible to transient failures.
For instance, this simple method invokes the underlying method up to maxAttempts times whenever an exception is thrown, unless it is a subclass of a Throwable listed in noRetryFor.
private Object doRetryWithExponentialBackoff(ProceedingJoinPoint pjp, int maxAttempts,
Class<? extends Throwable>[] noRetryFor) throws Throwable {
Throwable lastThrowable = null;
for (int attempts = 0; attempts < maxAttempts; attempts++) {
try {
pauseExponentially(attempts, lastThrowable);
return pjp.proceed();
} catch (Throwable t) {
lastThrowable = t;
for (Class<? extends Throwable> noRetryThrowable : noRetryFor) {
if (noRetryThrowable.isAssignableFrom(t.getClass())) {
throw t;
}
}
}
}
throw lastThrowable;
}
private void pauseExponentially(int attempts, Throwable lastThrowable) {
if (attempts == 0)
return;
long delay = (long) (Math.random() * (Math.pow(4, attempts) * 100L));
log.warn("Retriable error detected, will retry in " + delay + "ms, attempts thus far: "
+ attempts, lastThrowable);
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
// Nothing we need to do here
}
}
This advice could be applied to any bean you wish using Spring's Aspect support. See http://static.springsource.org/spring/docs/2.5.x/reference/aop.html for more details.
something like this:
private int retries;
/**
* Make this configurable.
*/
public void setRetries(final int retries) {
Assert.isTrue(retries > 0);
this.retries = retries;
}
public Object yourMethod() {
final int tries = 0;
Exception lastException = null;
for (int i = 0; i < this.retries; i++) {
try {
return jdbcTemplate.execute ... (your code here);
} catch (final SQLException e) {
lastException = e;
} catch (final DataAccessException e) {
lastException = e;
}
}
throw lastException;
}
How about writting an aspect (DBRetryAspect) over it;It will be more transparent.

Categories

Resources