Package freenet.support

Source Code of freenet.support.PrioritizedTicker$Job

package freenet.support;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.TreeMap;

import freenet.node.FastRunnable;
import freenet.support.Logger.LogLevel;
import freenet.support.io.NativeThread;

public class PrioritizedTicker implements Ticker, Runnable {
 
  private static volatile boolean logMINOR;

  static {
    Logger.registerLogThresholdCallback(new LogThresholdCallback(){
      @Override
      public void shouldUpdate(){
        logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
      }
    });
  }

  private final static class Job {
    final String name;
    final Runnable job;
    Job(String name, Runnable job) {
      this.name = name;
      this.job = job;
    }

    @Override
    public boolean equals(Object o) {
      if(!(o instanceof Job)) return false;
      // Ignore the name, we are only interested in the job, needed for noDupes.
      return ((Job)o).job == job;
    }
   
    @Override
    public int hashCode() {
      return job.hashCode();
    }
  }
 
  /** ~= Ticker :) */
  private final TreeMap<Long, Object> timedJobsByTime;
  private final HashMap<Job, Long> timedJobsQueued;
  final NativeThread myThread;
  final Executor executor;
  static final int MAX_SLEEP_TIME = 200;
 
  public PrioritizedTicker(Executor executor, int portNumber) {
    this.executor = executor;
    timedJobsByTime = new TreeMap<Long, Object>();
    timedJobsQueued = new HashMap<Job, Long>();
    myThread = new NativeThread(this, "Ticker thread for " + portNumber, NativeThread.MAX_PRIORITY, false);
    myThread.setDaemon(true);
  }
 
  public void start() {
    Logger.normal(this, "Starting Ticker");
    System.out.println("Starting Ticker");
    myThread.start();
  }

  @Override
  public void run() {
    if(logMINOR) Logger.minor(this, "In Ticker.run()");
    freenet.support.Logger.OSThread.logPID(this);
    while(true) {
      try {
        realRun();
      } catch(Throwable t) {
        Logger.error(this, "Caught in PacketSender: " + t, t);
        System.err.println("Caught in PacketSender: " + t);
        t.printStackTrace();
      }
    }
  }
 
  private void realRun() {
    long now = System.currentTimeMillis();
   
    List<Job> jobsToRun = null;
   
    long sleepTime = MAX_SLEEP_TIME;

    synchronized(timedJobsByTime) {
      while(!timedJobsByTime.isEmpty()) {
        Long tRun = timedJobsByTime.firstKey();
        if(tRun.longValue() <= now) {
          if(jobsToRun == null)
            jobsToRun = new ArrayList<Job>();
          Object o = timedJobsByTime.remove(tRun);
          if(o instanceof Job[]) {
            for(Job r: (Job[]) o) {
              jobsToRun.add(r);
              timedJobsQueued.remove(r);
            }
          } else {
            Job r = (Job) o;
            jobsToRun.add(r);
            timedJobsQueued.remove(r);
          }
        } else {
          sleepTime = Math.min(sleepTime, tRun.longValue() - now);
          break;
        }
      }
    }

    if(jobsToRun != null)
      for(Job r : jobsToRun) {
        if(logMINOR)
          Logger.minor(this, "Running " + r);
        if(r.job instanceof FastRunnable)
          // Run in-line

          try {
            r.job.run();
          } catch(Throwable t) {
            Logger.error(this, "Caught " + t + " running " + r, t);
          }
        else
          try {
            executor.execute(r.job, r.name, true);
          } catch(Throwable t) {
            Logger.error(this, "Caught in PacketSender: " + t, t);
            System.err.println("Caught in PacketSender: " + t);
            t.printStackTrace();
                                                System.err.println("Will retry above failed operation...");
                                                queueTimedJob(r.job, r.name, 200, true, false);
          }
      }

    if(sleepTime > 0) {
      // Update logging only when have time to do so
      try {
        if(logMINOR)
          Logger.minor(this, "Sleeping for " + sleepTime);
        synchronized(this) {
          wait(sleepTime);
        }
      } catch(InterruptedException e) {
        // Ignore, just wake up. Probably we got interrupt()ed
        // because a new job came in.
      }
    }
  }


  @Override
  public void queueTimedJob(Runnable job, long offset) {
    queueTimedJob(job, "Scheduled job: "+job, offset, false, false);
  }

  /**
   * Queue a job at a specific time.
   * @param runner The job to run. FastRunnable's get run directly on the PacketSender thread.
   * @param name The name of the job, the thread running it will temporarily take this name,
   * assuming it is run on a separate thread.
   * @param offset The time at which to run the job in milliseconds after
   * System.currentTimeMillis().
   * @param runOnTickerAnyway If false, run jobs with offset <=0 on the ticker, to preserve
   * their thread priorities; if true, jobs to run immediately through the executor (which
   * normally will also preserve thread priorities, but may need to call back via
   * runOnTickerAnyway=true if it needs to increase the thread priority).
   * @param noDupes Don't run this job if it is already scheduled. Relatively expensive, O(n)
   * with queued jobs. Necessary for Announcer to ensure that we don't get exponentially
   * increasing numbers of announcement check jobs queued, while ensuring that we do always
   * have one queued within the given period.
   */
  @Override
  public void queueTimedJob(Runnable runner, String name, long offset, boolean runOnTickerAnyway, boolean noDupes) {
    // Run directly *if* that won't cause any priority problems.
    if(offset <= 0 && !runOnTickerAnyway) {
      if(logMINOR) Logger.minor(this, "Running directly: "+runner);
      executor.execute(runner, name);
      return;
    }
    Job job = new Job(name, runner);
    if(offset < 0) offset = 0;
    long now = System.currentTimeMillis();
    Long l = Long.valueOf(offset + now);
    synchronized(timedJobsByTime) {
      if(noDupes) {
        Long t = timedJobsQueued.get(job);
        if(t != null) {
          if(t <= l) {
            Logger.normal(this, "Not re-running as already queued: "+runner+" for "+name);
            return;
          } else {
            // Delete the existing job because the new job will run first.
            Object o = timedJobsByTime.get(t);
            if(o instanceof Job) {
              timedJobsQueued.remove(job);
              assert(o.equals(job));
              timedJobsByTime.remove(t);
            } else {
              Job[] jobs = (Job[]) o;
              if(jobs.length == 1) {
                timedJobsQueued.remove(job);
                assert(jobs[0].equals(job));
                timedJobsByTime.remove(t);
              } else {
                Job[] newJobs = new Job[jobs.length-1];
                int x = 0;
                for(Job oldjob: jobs) {
                  if(oldjob.equals(job)) {
                    timedJobsQueued.remove(oldjob);
                    continue;
                  }
                  newJobs[x++] = oldjob;
                  if(x == jobs.length) {
                    assert(false);
                    newJobs = jobs;
                  }
                }
                if(x == 0) {
                  assert(false);
                  timedJobsByTime.remove(t);
                } else if (x == 1) {
                  timedJobsByTime.put(t, newJobs[0]);
                } else {
                  if(x != newJobs.length)
                    newJobs = Arrays.copyOf(newJobs, x);
                  timedJobsByTime.put(t, newJobs);
                  assert(x == jobs.length-1);
                }
              }
            }
          }
        }
      }
      Object o = timedJobsByTime.get(l);
      if(o == null)
        timedJobsByTime.put(l, job);
      else if(o instanceof Job)
        timedJobsByTime.put(l, new Job[]{(Job) o, job});
      else if(o instanceof Job[]) {
        Job[] r = (Job[]) o;
        Job[] jobs = Arrays.copyOf(r, r.length+1);
        jobs[jobs.length - 1] = job;
        timedJobsByTime.put(l, jobs);
      }
      timedJobsQueued.put(job, l);
    }
    if(offset < MAX_SLEEP_TIME) {
      wakeUp();
    }
  }
 
  /** Wake up, and run any queued jobs. */
  void wakeUp() {
    // Wake up if needed
    synchronized(this) {
      notifyAll();
    }
  }

  @Override
  public Executor getExecutor() {
    return executor;
  }

  public int queuedJobs() {
    synchronized(timedJobsByTime) {
      return timedJobsByTime.size();
    }
  }

  @Override
  public void removeQueuedJob(Runnable runnable) {
    Job job = new Job(null, runnable);
    synchronized(timedJobsByTime) {
      Long t = timedJobsQueued.remove(job);
      if(t != null) {
        Object o = timedJobsByTime.get(t);
        if(o == null) return; // XXX impossible -> assert
        if(o instanceof Job) {
          assert(o.equals(job));
          timedJobsByTime.remove(t);
        } else {
          Job[] jobs = (Job[]) o;
          if(jobs.length == 1) {
            assert(jobs[0].equals(job));
            timedJobsByTime.remove(t);
          } else {
            Job[] newJobs = new Job[jobs.length-1];
            int x = 0;
            for(Job oldjob : jobs) {
              if(oldjob.equals(job)) {
                continue;
              }
              newJobs[x++] = oldjob;
              if(x == jobs.length) {
                assert(false);
                newJobs = jobs;
              }
            }
            if(x == 0) {
              assert(false);
              timedJobsByTime.remove(t);
            } else if (x == 1) {
              timedJobsByTime.put(t, newJobs[0]);
            } else {
              if(x != newJobs.length)
                newJobs = Arrays.copyOf(newJobs, x);
              timedJobsByTime.put(t, newJobs);
              assert(x == jobs.length-1);
            }
          }
        }
      }
    }
  }
 
}
TOP

Related Classes of freenet.support.PrioritizedTicker$Job

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.