com.almende.eve.scheduling.SyncScheduler.java Source code

Java tutorial

Introduction

Here is the source code for com.almende.eve.scheduling.SyncScheduler.java

Source

/*
 * Copyright: Almende B.V. (2014), Rotterdam, The Netherlands
 * License: The Apache Software License, Version 2.0
 */
package com.almende.eve.scheduling;

import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.joda.time.DateTime;
import org.joda.time.Duration;

import com.almende.eve.capabilities.handler.Handler;
import com.almende.eve.protocol.jsonrpc.annotation.Access;
import com.almende.eve.protocol.jsonrpc.annotation.AccessType;
import com.almende.eve.protocol.jsonrpc.annotation.Namespace;
import com.almende.eve.protocol.jsonrpc.formats.Caller;
import com.almende.eve.scheduling.clock.RunnableClock;
import com.almende.util.jackson.JOM;
import com.almende.util.uuid.UUID;
import com.fasterxml.jackson.databind.node.ObjectNode;

/**
 * The Class SyncScheduler.
 */

@Namespace("syncScheduler")
public class SyncScheduler extends SimpleScheduler {
    private static final Logger LOG = Logger.getLogger(SyncScheduler.class.getName());
    private long offset = 0;
    private long syncInterval = 50000;
    private Caller caller = null;
    private Set<URI> peers = new HashSet<URI>();
    private Boolean active = false;

    @Override
    public long now() {
        return super.now() + offset;
    }

    @Override
    public DateTime nowDateTime() {
        return super.nowDateTime().plus(offset);
    }

    /**
     * Sets the caller.
     *
     * @param caller
     *            the new caller
     */
    public void setCaller(final Caller caller) {
        this.caller = caller;
    }

    /**
     * Adds the peer.
     *
     * @param peer
     *            the peer
     */
    public void addPeer(final URI peer) {
        if (!peers.contains(peer)) {
            peers.add(peer);
        }
        sync();
    }

    @Override
    public String schedule(final String id, final Object msg, final DateTime due) {
        final String uuid = id != null ? id : new UUID().toString();
        getClock().requestTrigger(uuid, due.minus(offset), new Runnable() {

            @Override
            public void run() {
                handleTrigger(msg, id);
            }

        });
        return uuid;
    }

    @Override
    public String schedule(final String id, final Object msg, final int delay) {
        return schedule(id, msg, new DateTime(now()).plus(delay));
    }

    /**
     * Instantiates a new persistent scheduler.
     * 
     * @param params
     *            the params
     * @param handle
     *            the handle
     */
    public SyncScheduler(final ObjectNode params, final Handler<Caller> handle) {
        super(params, handle);
        if (getClock() == null) {
            setClock(new RunnableClock());
        }
    }

    /**
     * Ping.
     *
     * @return the long
     */
    @Access(AccessType.PUBLIC)
    public Long ping() {
        return now();
    }

    class SyncTupple implements Comparable<SyncTupple> {
        long offset;
        long roundtrip;

        public SyncTupple(long offset, long roundtrip) {
            this.offset = offset;
            this.roundtrip = roundtrip;
        }

        @Override
        public int compareTo(SyncTupple o) {
            return roundtrip == o.roundtrip ? 0 : (roundtrip > o.roundtrip ? 1 : -1);
        }

        @Override
        public String toString() {
            return "{\"offset\":" + offset + ",\"roundtrip\":" + roundtrip + "}";
        }
    }

    /**
     * Sync with peer.
     *
     * @param peer
     *            the peer
     * @return the long
     */
    @Access(AccessType.PUBLIC)
    public SyncTupple syncWithPeer(final URI peer) {
        if (caller == null) {
            LOG.warning("Sync requested, but caller is still null, invalid!");
            return null;
        }
        LOG.info("Starting sync with: " + peer + "!");
        final DateTime start = DateTime.now();
        try {
            final Long result = caller.callSync(peer, "syncScheduler.ping", JOM.createObjectNode(), Long.class);
            final long roundtrip = new Duration(start, DateTime.now()).getMillis();
            final long offset = result - now() + (roundtrip / 2);
            LOG.info("Sync resulted in offset:" + offset + " ( " + roundtrip + ":" + start + ":" + result + ")");
            return new SyncTupple(offset, roundtrip);
        } catch (IOException e) {
            LOG.log(Level.WARNING, "failed to send ping", e);
        }
        return null;
    }

    /**
     * Sync.
     */
    @Access(AccessType.PUBLIC)
    public void sync() {
        synchronized (active) {
            if (active) {
                return;
            }
            active = true;
        }
        try {
            for (final URI peer : peers) {
                LOG.info("Doing sync with " + peer + "!");

                final ArrayList<SyncTupple> results = new ArrayList<SyncTupple>(5);
                final int[] fail = new int[1];
                fail[0] = 0;
                getClock().requestTrigger(new UUID().toString(), DateTime.now(), new Runnable() {
                    @Override
                    public void run() {
                        final SyncTupple res = syncWithPeer(peer);
                        if (res != null) {
                            results.add(res);
                        } else {
                            fail[0]++;
                        }
                        if (fail[0] < 5 && results.size() < 5) {
                            getClock().requestTrigger(new UUID().toString(),
                                    DateTime.now().plus((long) (4000 * Math.random())), this);
                        }
                    }
                });
                while (fail[0] < 5 && results.size() < 5) {
                    try {
                        Thread.sleep(4000);
                    } catch (InterruptedException e) {
                    }
                }
                long sum = 0;
                for (int i = 0; i < results.size(); i++) {
                    sum += results.get(i).roundtrip;
                }
                long mean = sum / results.size();

                sum = 0;
                for (int i = 0; i < results.size(); i++) {
                    sum += Math.pow(results.get(i).roundtrip - mean, 2);
                }

                double stdDev = Math.sqrt(sum / results.size());
                double limit = stdDev + mean;
                LOG.warning("Mean:" + mean + " stdDev:" + stdDev + " limit:" + limit);

                sum = 0;
                int count = 0;
                for (SyncTupple tupple : results) {
                    if (tupple.roundtrip > limit) {
                        LOG.warning("Skipping tupple:" + tupple);
                        continue;
                    }
                    LOG.warning("Adding tupple:" + tupple);
                    count++;
                    sum += tupple.offset;
                }

                offset += sum / count;
                LOG.info("Done sync with " + peer + "! new offset:" + offset + "(" + sum / count + ")");
            }
        } catch (Exception e) {
            LOG.log(Level.WARNING, "TimeSync failed", e);
        }
        synchronized (active) {
            active = false;
        }
        getClock().requestTrigger(new UUID().toString(),
                new DateTime(now()).plus((long) (syncInterval * Math.random())), new Runnable() {
                    @Override
                    public void run() {
                        try {
                            sync();
                        } catch (Exception e) {
                            LOG.log(Level.WARNING, "sync failed", e);
                        }
                    }
                });

    }
}