package com.robonobo.core; import java.io.File; import java.io.IOException; import java.net.*; import java.nio.ByteBuffer; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.log4j.BasicConfigurator; import com.robonobo.common.concurrent.Attempt; import com.robonobo.common.exceptions.SeekInnerCalmException; import com.robonobo.common.serialization.ConfigBeanSerializer; import com.robonobo.eon.*; import com.robonobo.mina.external.MinaConfig; /** * Handles detecting running rbnb instances, and if necessary hand-offs between them * * @author macavity * */ public class RobonoboRuntime { private File homeDir; public RobonoboRuntime(File homeDir) { this.homeDir = homeDir; } /** * If another instance is running, tries to handover to them, and returns true, otherwise returns false * * @throws IOException * Something went wrong on the filesystem * @throws EONException * The handover didn't work properly - there is another robonobo instance running, but it's not * responding * */ public boolean handoverIfRunning(String arg) throws IOException, EONException { // Look up our port from the mina config (overridden from env if necessary) int udpPort = -1; if(homeDir.exists()) { File cfgDir = new File(homeDir, "config"); if(cfgDir.exists()) { File minaCfgFile = new File(cfgDir, "mina.cfg"); if(minaCfgFile.exists()) { ConfigBeanSerializer cbs = new ConfigBeanSerializer(false); MinaConfig cfg = cbs.deserializeConfig(MinaConfig.class, minaCfgFile); cbs.overrideCfgFromEnv(cfg, "mina"); udpPort = cfg.getListenUdpPort(); } } } if(udpPort > 0) { // Let's try and listen on this port - if we get an exception, they're listening // A bit fugly to use exceptions to do this, but seems the easiest way... try { // Use wildcard address DatagramSocket sock = new DatagramSocket(udpPort); sock.close(); } catch (SocketException e) { handover(udpPort, arg); return true; } } return false; } private void handover(int theirUdpPort, String arg) throws EONException { if (arg == null) arg = ""; // Bring up a minimal log4j so eon doesn't shit itself BasicConfigurator.configure(); // Start an eonmanager, and send them our arg via the local request service ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1); EONManager eonMgr = new EONManager("handover", executor); eonMgr.start(); try { final DEONConnection conn = eonMgr.createDEONConnection(); EonSocketAddress theirSockAddr; try { theirSockAddr = new EonSocketAddress(theirUdpPort, 1); } catch (UnknownHostException e) { throw new SeekInnerCalmException(); } conn.bind(); conn.sendTo(theirSockAddr, arg.getBytes()); // We should get back a string <num>:<msg>, where <num> is 0 on success or non-zero on error Attempt attempt = new Attempt(executor, 30000, "handoverAttempt") { protected void onTimeout() { conn.close(); } }; attempt.start(); ByteBuffer retBuf = (ByteBuffer) conn.read()[0]; attempt.succeeded(); String retStr = new String(retBuf.array(), retBuf.arrayOffset(), retBuf.remaining()); Pattern respPat = Pattern.compile("^(\\d+):(.*)$"); Matcher m = respPat.matcher(retStr); if (m.matches()) { int status = Integer.parseInt(m.group(1)); if (status == 0) { System.out.println("robonobo: handed over arg '" + arg + "' to local instance on port " + theirUdpPort + ": exiting"); } else System.out.println("robonobo got error handing over arg '"+arg+"': " + m.group(2)); return; } else throw new EONException("Received unexpected response to handover: "+retStr); } catch (InterruptedException e) { throw new EONException("Handover timed out"); } finally { eonMgr.stop(); } } }