/*******************************************************************************
* Copyright (c) 2010 Oak Ridge National Laboratory.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
******************************************************************************/
package org.epics.archiverappliance.engine.pv;
import gov.aps.jca.Context;
import gov.aps.jca.JCALibrary;
import gov.aps.jca.configuration.Configuration;
import gov.aps.jca.configuration.DefaultConfigurationBuilder;
import gov.aps.jca.event.ContextExceptionListener;
import gov.aps.jca.event.ContextMessageListener;
import java.io.ByteArrayInputStream;
import java.util.LinkedList;
import org.apache.log4j.Logger;
import org.epics.archiverappliance.config.ConfigService;
import org.epics.archiverappliance.engine.epics.JCAConfigGen;
import org.epics.archiverappliance.engine.model.ContextErrorHandler;
/**
* JCA command pump, added for two reasons:
* <ol>
* <li>JCA callbacks can't directly send JCA commands without danger of a
* deadlock, at least not with JNI and the "DirectRequestDispatcher".
* <li>Instead of calling 'flushIO' after each command, this thread allows for a
* few requests to queue up, then periodically pumps them out with only a final
* 'flush'
* </ol>
*
* @author Kay Kasemir
* @version Initial version:CSS
* @version 4-Jun-2012, Luofeng Li:added codes to support for the new archiver
*/
@SuppressWarnings("nls")
public class JCACommandThread extends Thread {
/**
* Delay between queue inspection. Longer delay results in bigger 'batches',
* which is probably good, but also increases the latency.
*/
final private static long DELAY_MILLIS = 100;
private static final Logger logger = Logger.getLogger(JCACommandThread.class.getName());
/** The JCA Context */
private volatile Context jca_context = null;
/** The Java CA Library instance. */
private JCALibrary jca = null;
/**
* Command queue.
* <p>
* SYNC on access
*/
final private LinkedList<Runnable> command_queue = new LinkedList<Runnable>();
/** Maximum size that command_queue reached at runtime */
private int max_size_reached = 0;
/** Flag to tell thread to run or quit */
private boolean run = false;
private ConfigService configService;
/**
* Construct, but don't start the thread.
*
* @param configService ConfigService
* @see #start()
*/
public JCACommandThread(ConfigService configService) {
super("JCA Command Thread");
// this.jca_context = jca_context;
this.configService = configService;
}
Context getContext() {
return jca_context;
}
private void initContext() {
try {
if (jca == null) {
ByteArrayInputStream bis = JCAConfigGen.generateJCAConfig(configService);
jca = JCALibrary.getInstance();
DefaultConfigurationBuilder configBuilder = new DefaultConfigurationBuilder();
Configuration configuration;
configuration = configBuilder.build(bis);
jca_context = jca.createContext(configuration);
// Per default, JNIContext adds a logger to System.err,
// but we want this one:
final ContextErrorHandler log_handler = new ContextErrorHandler();
jca_context.addContextExceptionListener(log_handler);
jca_context.addContextMessageListener(log_handler);
// Debugger shows that JNIContext adds the System.err
// loggers during initialize(), which for example happened
// in response to the last addContext... calls, so fix
// it after the fact:
final ContextExceptionListener[] ex_lsnrs = jca_context
.getContextExceptionListeners();
for (ContextExceptionListener exl : ex_lsnrs)
if (exl != log_handler)
jca_context.removeContextExceptionListener(exl);
// Same with message listeners
final ContextMessageListener[] msg_lsnrs = jca_context
.getContextMessageListeners();
for (ContextMessageListener cml : msg_lsnrs)
if (cml != log_handler)
jca_context.removeContextMessageListener(cml);
}
} catch (Exception e) {
//
logger.error("exception when initing Context in JCACommandThread",
e);
}
}
/**
* Version of <code>start</code> that may be called multiple times.
* <p>
* The thread must only be started after the first PV has been created.
* Otherwise, if flush is called without PVs, JNI JCA reports pthread
* errors.
* <p>
* NOP when already running
*/
@Override
public synchronized void start() {
if (run)
return;
run = true;
super.start();
}
/**
* Stop the thread and wait for it to finish
*
* @throws InterruptedException
*/
public void shutdown() throws InterruptedException {
// destory context
destoryContext();
for (int m = 0; m < 30; m++) {
Thread.sleep(1000);
if (command_queue.size() == 0)
break;
}
run = false;
}
/**
* Add a command to the queue. add some cap on the command queue? At least
* for value updates?
*
* @param command Runnable
*/
public void addCommand(final Runnable command) {
synchronized (command_queue) {
// New maximum queue length (+1 for the one about to get added)
if (command_queue.size() >= max_size_reached)
max_size_reached = command_queue.size() + 1;
command_queue.addLast(command);
}
}
/** @return Oldest queued command or <code>null</code> */
private Runnable getCommand() {
synchronized (command_queue) {
if (command_queue.size() > 0)
return command_queue.removeFirst();
}
return null;
}
@Override
public void run() {
initContext();
while (run) {
// Execute all the commands currently queued...
Runnable command = getCommand();
while (command != null) { // Execute one command
try {
command.run();
} catch (Throwable ex) {
logger.error("exception when command runs in JCACommandThread",
ex);
}
// Get next command
command = getCommand();
}
// Flush.
// Once, after executing all the accumulated commands.
// Even when the command queue was empty,
// there may be stuff worth flushing.
try {
if(jca_context!=null)jca_context.flushIO();
} catch (Throwable ex) {
logger.error("exception when flushing io in JCACommandThread",
ex);
}
// Then wait.
try {
Thread.sleep(DELAY_MILLIS);
} catch (InterruptedException ex) { /* don't even ignore */
logger.error("exception when thread sleeping in JCACommandThread",
ex);
}
}
}
void destoryContext() {
addCommand(new Runnable()
{
@Override
public void run() {
try {
if (jca_context != null) {
jca_context.destroy();
jca_context = null;
jca = null;
}
} catch (Exception ex) {
logger.error("exception when destorying context in JCACommandThread", ex);
}
}
});
}
}