All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.yamcs.tctm.AbstractThreadedTcDataLink Maven / Gradle / Ivy

There is a newer version: 5.10.9
Show newest version
package org.yamcs.tctm;

import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import org.yamcs.ConfigurationException;
import org.yamcs.Spec;
import org.yamcs.Spec.OptionType;
import org.yamcs.YConfiguration;
import org.yamcs.commanding.PreparedCommand;

import com.google.common.util.concurrent.RateLimiter;

/**
 * Abstract link that starts a thread when it's enabled and stops it when it's disabled.
 * 

* The {@link #startUp()} and {@link #shutDown()} methods are called at startup/enable and shutdown/disable times on the * working thread. * *

* This class provides queueing and rate limiting function. *

* args: *

    *
  • tcQueueSize: maximum size of the queue. If the queue is full, the commands will be rejected. If the argument is * not specified, the queue will be unlimited in size.
  • *
  • tcMaxRate: maximum number of commands to send per second.
  • *
* */ public abstract class AbstractThreadedTcDataLink extends AbstractTcDataLink implements Runnable { Thread thread; RateLimiter rateLimiter; protected BlockingQueue commandQueue; // the initial delay applies only if the link is enabled at startup long initialDelay; @Override public Spec getDefaultSpec() { var spec = super.getDefaultSpec(); spec.addOption("tcQueueSize", OptionType.INTEGER); spec.addOption("tcMaxRate", OptionType.INTEGER); spec.addOption("initialDelay", OptionType.INTEGER); return spec; } @Override public void init(String yamcsInstance, String linkName, YConfiguration config) throws ConfigurationException { super.init(yamcsInstance, linkName, config); if (config.containsKey("tcQueueSize")) { commandQueue = new LinkedBlockingQueue<>(config.getInt("tcQueueSize")); } else { commandQueue = new LinkedBlockingQueue<>(); } initialDelay = config.getLong("initialDelay", 0); if (config.containsKey("tcMaxRate")) { rateLimiter = RateLimiter.create(config.getInt("tcMaxRate")); } } @Override protected void doStart() { if (!isDisabled()) { doEnable(); } else { initialDelay = 0; } notifyStarted(); } @Override protected void doStop() { if (!isDisabled()) { try { shutDown(); commandQueue.clear(); commandQueue.offer(SIGNAL_QUIT); try { thread.join(); } catch (InterruptedException e) { log.warn("Interrupted while waiting for thread shutdown"); Thread.currentThread().interrupt(); } notifyStopped(); } catch (Exception e) { notifyFailed(e); } } else { notifyStopped(); } } /** * Sends */ @Override public boolean sendCommand(PreparedCommand pc) { if (!commandQueue.offer(pc)) { log.warn("Cannot put command {} in the queue, because it's full; sending NACK", pc); commandHistoryPublisher.commandFailed(pc.getCommandId(), getCurrentTime(), "Link " + linkName + ": queue full"); } return true; } @Override public void run() { if (initialDelay > 0) { try { Thread.sleep(initialDelay); initialDelay = 0; } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } } try { startUp(); } catch (Exception e) { log.error("Failed to startUp", e); } while (isRunningAndEnabled()) { doHousekeeping(); try { PreparedCommand pc = commandQueue.poll(housekeepingInterval, TimeUnit.MILLISECONDS); if (pc == null) { continue; } if (pc == SIGNAL_QUIT) { return; } if (rateLimiter != null) { rateLimiter.acquire(); } uplinkCommand(pc); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } catch (Exception e) { log.error("Error when sending command: ", e); throw new RuntimeException(e); } } try { shutDown(); } catch (Exception e) { log.error("Failed to shutDown", e); // TODO we should effectively fail the service here but we cannot because we already notified started // so we disable it instead disable(); } } @Override protected void doEnable() { thread = new Thread(this); thread.setName(getClass().getSimpleName() + "-" + linkName); thread.start(); } @Override protected void doDisable() { if (thread != null) { thread.interrupt(); } } /** * Called each {@link #housekeepingInterval} milliseconds, can be used to establish tcp connections or similar * things */ protected void doHousekeeping() { } /** * Called * * @param pc * @throws IOException */ protected abstract void uplinkCommand(PreparedCommand pc) throws IOException; /** * Called at start up (if the link is enabled) or when the link is enabled * * @throws Exception */ protected abstract void startUp() throws Exception; /** * Called at shutdown (if the link is enabled) or when the link is disabled * * @throws Exception */ protected abstract void shutDown() throws Exception; }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy