package lbms.plugins.mldht.kad; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import lbms.plugins.mldht.kad.DHT.LogLevel; import lbms.plugins.mldht.kad.messages.MessageBase; /** * Performs a task on K nodes provided by a KClosestNodesSearch. * This is a base class for all tasks. * * @author Damokles */ public abstract class Task implements RPCCallListener { protected List<KBucketEntry> visited; // nodes visited protected SortedSet<KBucketEntry> todo; // nodes todo protected Node node; protected Key targetKey; protected String info; protected RPCServerBase rpc; private AtomicInteger outstandingReqs = new AtomicInteger(); private int sentReqs; private int recvResponses; private int failedReqs; private int taskID; private boolean taskFinished; private boolean queued; private List<TaskListener> listeners; private ScheduledFuture<?> timeoutTimer; /** * Create a task. * @param rpc The RPC server to do RPC calls * @param node The node */ Task (Key target, RPCServerBase rpc, Node node) { this.targetKey = target; this.rpc = rpc; this.node = node; queued = true; todo = new TreeSet<KBucketEntry>(new KBucketEntry.DistanceOrder(targetKey)); visited = new ArrayList<KBucketEntry>(); taskFinished = false; } /** * @param rpc The RPC server to do RPC calls * @param node The node * @param info info that should be displayed to the user, eg. download name on announce task */ Task (Key target, RPCServerBase rpc, Node node, String info) { this(target, rpc, node); this.info = info; } /* (non-Javadoc) * @see lbms.plugins.mldht.kad.RPCCallListener#onResponse(lbms.plugins.mldht.kad.RPCCall, lbms.plugins.mldht.kad.messages.MessageBase) */ public void onResponse (RPCCallBase c, MessageBase rsp) { outstandingReqs.decrementAndGet(); recvResponses++; if (!isFinished()) { callFinished(c, rsp); if (canDoRequest() && !isFinished()) { update(); } } } /* (non-Javadoc) * @see lbms.plugins.mldht.kad.RPCCallListener#onTimeout(lbms.plugins.mldht.kad.RPCCall) */ public void onTimeout (RPCCallBase c) { outstandingReqs.decrementAndGet(); failedReqs++; if (!isFinished()) { callTimeout(c); if (canDoRequest() && !isFinished()) { update(); } } } /** * Start the task, to be used when a task is queued. */ void start () { if (queued) { DHT.logDebug("Starting Task: " + this.getClass().getSimpleName() + " TaskID:" + taskID); queued = false; startTimeout(); try { update(); } catch (Exception e) { DHT.log(e, LogLevel.Error); } } } /** * Will continue the task, this will be called every time we have * rpc slots available for this task. Should be implemented by derived classes. */ abstract void update (); /** * A call is finished and a response was received. * @param c The call * @param rsp The response */ abstract void callFinished (RPCCallBase c, MessageBase rsp); /** * A call timedout * @param c The call */ abstract void callTimeout (RPCCallBase c); /** * Do a call to the rpc server, increments the outstanding_reqs variable. * @param req THe request to send * @return true if call was made, false if not */ boolean rpcCall (MessageBase req) { if (!canDoRequest()) { return false; } RPCCallBase c = rpc.doCall(req); c.addListener(this); outstandingReqs.incrementAndGet(); sentReqs++; return true; } /// See if we can do a request boolean canDoRequest () { return outstandingReqs.get() < DHTConstants.MAX_CONCURRENT_REQUESTS; } /// Is the task finished public boolean isFinished () { return taskFinished; } /// Set the task ID void setTaskID (int tid) { taskID = tid; } /// Get the task ID public int getTaskID () { return taskID; } /** * @return the Count of Failed Requests */ public int getFailedReqs () { return failedReqs; } /** * @return the Count of Received Responses */ public int getRecvResponses () { return recvResponses; } /** * @return the Count of Sent Requests */ public int getSentReqs () { return sentReqs; } public int getTodoCount () { return todo.size(); } /** * @return the targetKey */ public Key getTargetKey () { return targetKey; } /** * @return the info */ public String getInfo () { return info; } /** * @param info the info to set */ public void setInfo (String info) { this.info = info; } public void addToTodo (KBucketEntry e) { synchronized (todo) { todo.add(e); } } /// Get the number of outstanding requests public int getNumOutstandingRequests () { return outstandingReqs.get(); } public boolean isQueued () { return queued; } /// Kills the task void kill () { taskFinished = true; finished(this); } /** * Starts the Timeout Timer */ private void startTimeout () { DHT.getScheduler().schedule(new Runnable() { /* (non-Javadoc) * @see java.lang.Runnable#run() */ public void run () { if (!taskFinished) { DHT.logDebug("Task was Killed by Timeout. TaskID: " + taskID); kill(); } } }, DHTConstants.TASK_TIMEOUT, TimeUnit.MILLISECONDS); } /** * Add a node to the todo list * @param ip The ip or hostname of the node * @param port The port */ void addDHTNode (InetAddress ip, int port) { InetSocketAddress addr = new InetSocketAddress(ip, port); synchronized (todo) { todo.add(new KBucketEntry(addr, Key.createRandomKey())); } } /** * The task is finsihed. * @param t The Task */ private void finished (Task t) { DHT.logDebug("Task finished: " + getTaskID()); if (timeoutTimer != null) { timeoutTimer.cancel(false); } if (listeners != null) { for (TaskListener tl : listeners) { tl.finished(this); } } } protected void done () { taskFinished = true; finished(this); } public void addListener (TaskListener listener) { if (listeners == null) { listeners = new ArrayList<TaskListener>(1); } // listener is added after the task already terminated, thus it won't get the event, trigger it manually if(taskFinished) listener.finished(this); listeners.add(listener); } public void removeListener (TaskListener listener) { if (listeners != null) { listeners.remove(listener); } } }