/*******************************************************************************
* ALMA - Atacama Large Millimeter Array
* Copyright (c) ESO - European Southern Observatory, 2011
* (in the framework of the ALMA collaboration).
* All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*******************************************************************************/
package acs.benchmark.nc.comp.publisher;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor.AbortPolicy;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import org.omg.CORBA.portable.IDLEntity;
import acs.benchmark.nc.comp.CorbaNotifyBaseImpl;
import alma.ACSErrTypeCommon.CouldntPerformActionEx;
import alma.ACSErrTypeCommon.wrappers.AcsJCouldntPerformActionEx;
import alma.ACSErrTypeCommon.wrappers.AcsJIllegalStateEventEx;
import alma.JavaContainerError.wrappers.AcsJContainerServicesEx;
import alma.acs.exceptions.AcsJException;
import alma.acs.logging.AcsLogLevel;
import alma.acs.nc.AcsEventPublisher;
import alma.acs.util.StopWatch;
import alma.benchmark.CorbaNotifySupplierOperations;
import alma.benchmark.LightweightMountStatusData;
import alma.benchmark.MountStatusData;
import alma.benchmark.NcEventSpec;
import alma.benchmark.SomeOtherEventType;
public class CorbaNotifySupplierImpl extends CorbaNotifyBaseImpl<AcsEventPublisher<IDLEntity>> implements CorbaNotifySupplierOperations
{
/**
* One runnable per NC. Each run() call publishes an event.
*/
private final List<PublishEventRunnable> runnables = new ArrayList<PublishEventRunnable>();
private final List<ExecutorService> runnersToInterrupt = new ArrayList<ExecutorService>();
// @Override
// public void initialize(ContainerServices containerServices) throws ComponentLifecycleException {
// super.initialize(containerServices);
// }
//
// @Override
// public void cleanUp() throws AcsJComponentCleanUpEx {
// super.cleanUp();
// }
@Override
protected AcsEventPublisher<IDLEntity> createNcParticipant(String ncName) throws AcsJContainerServicesEx {
return m_containerServices.createNotificationChannelPublisher(ncName, IDLEntity.class);
}
@Override
protected void disconnectNcParticipant(AcsEventPublisher<IDLEntity> pub) throws AcsJIllegalStateEventEx {
pub.disconnect();
}
/**
* Runnable that can repeatedly publish the same (or an alternating series of different) events to a given NC.
* The run method expects to be called multiple times. If a finite number of events is specified to be published,
* and the {@link #setScheduledFuture(ScheduledFuture)} method has been called with the corresponding future object
* from an executor, then this runnable will cancel further executions once all events are sent.
*/
protected class PublishEventRunnable implements Runnable {
private final Object ncName;
/**
* Event data that will be published alternatingly
*/
private final List<IDLEntity> events;
private final AcsEventPublisher<IDLEntity> pub;
private final int eventsMax;
private volatile int eventsSent;
private ScheduledFuture<?> future;
private int lastEventIndex = -1;
PublishEventRunnable(NcEventSpec spec, AcsEventPublisher<IDLEntity> pub, int numberOfEvents) {
if (spec.ncName == null || spec.ncName.isEmpty()) {
throw new IllegalArgumentException("No NC specified");
}
if (spec.eventNames.length == 0) {
throw new IllegalArgumentException("No events specified for NC " + spec.ncName);
}
if (pub == null) {
throw new IllegalArgumentException("AcsEventPublisher was null for NC " + spec.ncName);
}
this.ncName = spec.ncName;
this.events = new ArrayList<IDLEntity>(spec.eventNames.length);
for (String eventName : spec.eventNames) {
IDLEntity event = createTestEvent(eventName, spec.antennaName);
events.add(event);
}
this.pub = pub;
this.eventsMax = numberOfEvents;
}
/**
* Support self-cancelling of repeated executions from inside the {@link #run()} method,
* in case that {@link #eventsMax} is >= 0 and we have already sent the requested
* number of events.
*/
void setScheduledFuture(ScheduledFuture<?> future) {
this.future = future;
}
/**
* Allows cancellation of the running event suppliers.
* A single call to the NC libs will not be interrupted though.
*/
void cancelPeriodicRuns() {
if (future != null) {
future.cancel(false);
}
}
@Override
public void run() {
// Alternatingly publish different events, trusting that "events" is not an empty list...
if (lastEventIndex >= events.size()-1) {
lastEventIndex = 0;
}
else {
lastEventIndex++;
}
try {
IDLEntity eventToPublish = events.get(lastEventIndex);
pub.publishEvent(eventToPublish);
eventsSent++;
// progress logging
int modDiv = logMultiplesOfEventCount.get();
if (modDiv > 0 && eventsSent % modDiv == 0) {
m_logger.log(AcsLogLevel.DEBUG, "Have published a total of " + eventsSent + " events on NC " + ncName +
", last was a " + eventToPublish.getClass().getSimpleName());
}
if (eventsMax > 0 && eventsSent == eventsMax) {
m_logger.info("Supplier for NC '" + ncName + "' done sending " + eventsMax + " events.");
cancelPeriodicRuns();
}
} catch (AcsJException ex) {
m_logger.log(Level.WARNING, "Publishing event failed for NC " + ncName, ex);
}
}
}
/**
* Factory method for event data that will be sent to the NCs.
* @param eventName
* @param antennaName Only used for eventName == MountStatusData or LightweightMountStatusData
* @return
* @throws IllegalArgumentException
*/
protected IDLEntity createTestEvent(String eventName, String antennaName) {
IDLEntity ret = null;
String antennaNameToSet = ( antennaName != null && !antennaName.isEmpty() ? antennaName : "unknown" );
if (eventName.equals("MountStatusData")) {
MountStatusData data = new MountStatusData();
data.antennaName = antennaNameToSet;
// @TODO set some of the boolean / double fields
ret = data;
}
else if (eventName.equals("LightweightMountStatusData")) {
LightweightMountStatusData data = new LightweightMountStatusData();
data.antennaName = antennaNameToSet;
ret = data;
}
else if (eventName.equals("SomeOtherEventType")) {
SomeOtherEventType data = new SomeOtherEventType();
ret = data;
}
// @TODO Add support for more event types as needed
else {
throw new IllegalArgumentException("Unsupported event type '" + eventName + "'.");
}
return ret;
}
@Override
public int sendEvents(NcEventSpec[] ncEventSpecs, int eventPeriodMillis, int numberOfEvents)
throws CouldntPerformActionEx {
if (cancel) {
AcsJCouldntPerformActionEx ex = new AcsJCouldntPerformActionEx("Method sendEvents cannot be called after interrupt / ncDisconnect.");
throw ex.toCouldntPerformActionEx();
}
StopWatch sw = null;
ScheduledThreadPoolExecutor runner = null;
// Set up the runnables for all NCs
try {
for (NcEventSpec ncEventSpec : ncEventSpecs) {
PublishEventRunnable runnable = new PublishEventRunnable(
ncEventSpec,
this.subsOrPubs.get(ncEventSpec.ncName),
numberOfEvents );
runnables.add(runnable);
}
// multithreaded executor
runner = new ScheduledThreadPoolExecutor(
ncEventSpecs.length, // thread per NC
m_containerServices.getThreadFactory(),
new AbortPolicy() ); //RejectedExecutionException
sw = new StopWatch();
// run the NC suppliers
for (PublishEventRunnable runnable : runnables) {
ScheduledFuture<?> future = null;
if (eventPeriodMillis > 0) {
// publish at fixed rate
future = runner.scheduleAtFixedRate(runnable, 0, eventPeriodMillis, TimeUnit.MILLISECONDS);
}
else {
// run continuously
future = runner.scheduleWithFixedDelay(runnable, 0, 1, TimeUnit.NANOSECONDS); // delay must be > 0, otherwise IllegalArgumentException
}
runnable.setScheduledFuture(future);
}
} catch (Exception ex) {
m_logger.log(AcsLogLevel.SEVERE, "sendEvents call failed", ex);
throw new AcsJCouldntPerformActionEx(ex).toCouldntPerformActionEx();
}
String msgBase = "Started publishing events on " + ncEventSpecs.length + " NC(s), sending events "
+ ( eventPeriodMillis > 0 ? "every " + eventPeriodMillis + " ms. " : "as fast as possible. " );
if (numberOfEvents > 0) {
m_logger.info(msgBase + "Will now wait until " + numberOfEvents + " have been published on every NC...");
// block until all events are sent
runner.setContinueExistingPeriodicTasksAfterShutdownPolicy(true);
runner.setExecuteExistingDelayedTasksAfterShutdownPolicy(true);
runner.shutdown();
try {
// 10 min timeout, just to clean up resources eventually.
// TODO: Offer a workaround for special long-running tests
boolean cleanTermination = runner.awaitTermination(10, TimeUnit.MINUTES);
if (!cleanTermination) {
m_logger.warning("Unforeseen termination of event suppliers after 10 min (timeout).");
cancel = true;
}
} catch (InterruptedException ex) {
cancel = true;
}
}
else {
runnersToInterrupt.add(runner);
m_logger.info(msgBase + "Will return and asynchronously continue publishing events, until interrupt() gets called.");
}
if (cancel) {
throw new AcsJCouldntPerformActionEx("Event sending was interrupted or failed otherwise.").toCouldntPerformActionEx();
}
return (int) sw.getLapTimeMillis();
}
@Override
public void interrupt() {
super.interrupt();
for (PublishEventRunnable runnable : runnables) {
runnable.cancelPeriodicRuns();
}
for (ExecutorService runner : runnersToInterrupt) {
runner.shutdown(); // to avoid the log "Forcibly terminating surviving thread" at a subsequent component deactivation
try {
runner.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException ex) {
m_logger.log(Level.WARNING, "", ex);
}
}
runnersToInterrupt.clear();
m_logger.info("Stopped publishing events.");
}
}