Author: robert
Date: 2008-02-09 00:19:38 +0000 (Sat, 09 Feb 2008)
New Revision: 17727
Modified:
trunk/freenet/src/freenet/node/NetworkIDManager.java
trunk/freenet/src/freenet/node/PeerNode.java
Log:
initial network-coloring implementation (too sensitive)
Modified: trunk/freenet/src/freenet/node/NetworkIDManager.java
===================================================================
--- trunk/freenet/src/freenet/node/NetworkIDManager.java 2008-02-08
23:41:38 UTC (rev 17726)
+++ trunk/freenet/src/freenet/node/NetworkIDManager.java 2008-02-09
00:19:38 UTC (rev 17727)
@@ -2,10 +2,12 @@
package freenet.node;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import freenet.io.comm.DMT;
import freenet.io.comm.DisconnectedException;
@@ -32,6 +34,7 @@
//Intervals between connectivity checks and NetworkID reckoning.
//Checks for added peers may be delayed up to LONG_PERIOD, so don't
make it too long.
+ //Coincedently, LONG_PERIOD is also the interval at which we send out
FNPNetworkID reminders.
private static final long BETWEEN_PEERS = 2000;
private static final long STARTUP_DELAY = 20000;
private static final long LONG_PERIOD = 120000;
@@ -42,6 +45,8 @@
private static final int NO_NETWORKID = 0;
+ //The minimum number of pings per-node we will try and send out before
doing any kind of network id reasoning.
+ private static final int MIN_PINGS_FOR_STARTUP=3;
//The number of pings, etc. beyond which is considered a sane value to
start experimenting from.
private static final int COMFORT_LEVEL=20;
@@ -60,7 +65,7 @@
node.getTicker().queueTimedJob(new Runnable() {
public void run() {
checkAllPeers();
- startupChecks =
node.peers.quickCountConnectedPeers();
+ startupChecks =
node.peers.quickCountConnectedPeers() * MIN_PINGS_FOR_STARTUP;
Logger.normal(NetworkIDManager.this,
"Past startup delay, "+startupChecks+" connected peers");
reschedule(0);
}
@@ -444,11 +449,16 @@
synchronized (workQueue) {
if (workQueue.isEmpty()) {
//Add two random peers to check next time, or
maybe... all of them?
- PeerNode p1=node.peers.getRandomPeer();
- PeerNode p2=node.peers.getRandomPeer(p1);
- addWorkToLockedQueue(p1);
- addWorkToLockedQueue(p2);
- reschedule(LONG_PERIOD);
+ if (startupChecks>0) {
+ checkAllPeers();
+ reschedule(BETWEEN_PEERS);
+ } else {
+ PeerNode p1=node.peers.getRandomPeer();
+ PeerNode
p2=node.peers.getRandomPeer(p1);
+ addWorkToLockedQueue(p1);
+ addWorkToLockedQueue(p2);
+ reschedule(LONG_PERIOD);
+ }
} else {
reschedule(BETWEEN_PEERS);
}
@@ -534,6 +544,15 @@
}
public void checkAllPeers() {
+ Iterator i=getAllConnectedPeers().iterator();
+ synchronized (workQueue) {
+ while (i.hasNext()) {
+ addWorkToLockedQueue((PeerNode)i.next());
+ }
+ }
+ }
+
+ private HashSet getAllConnectedPeers() {
double randomTarget=node.random.nextDouble();
HashSet connectedPeers = new HashSet();
PeerNode next = node.peers.closerPeer(null, connectedPeers,
null, randomTarget, true, false, -1, null, null);
@@ -541,12 +560,7 @@
connectedPeers.add(next);
next = node.peers.closerPeer(null, connectedPeers,
null, randomTarget, true, false, -1, null, null);
}
- Iterator i=connectedPeers.iterator();
- synchronized (workQueue) {
- while (i.hasNext()) {
- addWorkToLockedQueue((PeerNode)i.next());
- }
- }
+ return connectedPeers;
}
/**
@@ -556,9 +570,162 @@
* we have ping records for this time around; even if it is just
totally madeup identifiers.
*/
private void doNetworkIDReckoning(boolean anyPingChanges) {
- // [...!!!...] \\
+ //!!!: This is where the magic separation logic begins.
+ // This may still need a lot of work; e.g. a locking mechanism,
considering disconnected peers?
+ List newNetworkGroups=new ArrayList();
+ HashSet all=getAllConnectedPeers();
+ HashSet todo=(HashSet)all.clone();
+ HashSet takenNetworkIds=new HashSet();
+
+ synchronized (dontStartPlease) {
+ inTransition=true;
+ }
+
+ if (todo.isEmpty())
+ return;
+
+ //optimization, if no stats have changed, just rescan the list
consensus?
+
+ //Note that in all this set manipulation, we never consult in
what group a user previously was.
+ while (!todo.isEmpty()) {
+ PeerNode mostConnected=findMostConnectedPeerInSet(todo,
all);
+ PeerNetworkGroup newGroup = new PeerNetworkGroup();
+ newNetworkGroups.add(newGroup);
+ todo.remove(mostConnected);
+ //NB: as a side effect, this function will
automatically remove the members from 'todo'.
+ List members=xferConnectedPeerSetFor(mostConnected,
todo);
+ members.add(mostConnected);
+ newGroup.setMembers(members);
+ newGroup.setForbiddenIds(takenNetworkIds);
+
+ int id=newGroup.getConsensus();
+ if (id==NO_NETWORKID)
+ id=node.random.nextInt();
+ newGroup.assignNetworkId(id);
+ takenNetworkIds.add(new Integer(id));
+ }
+
+ //for now, we'll just say we are in our most-connected group.
really it needs to be most-successful, or dungeons may support themselves!
+ PeerNetworkGroup
ourgroup=(PeerNetworkGroup)newNetworkGroups.get(0);
+ ourgroup.ourGroup=true;
+ ourNetworkId=ourgroup.networkid;
+
+ Logger.error(this, "I am in network: "+ourNetworkId+", and have
divided my "+all.size()+" peers into "+newNetworkGroups.size()+" network
groups");
+
+ networkGroups=newNetworkGroups;
+
+ inTransition=false;
}
+ // Returns the 'best-connected' peer in the given set, or null if the
set is empty.
+ private PeerNode findMostConnectedPeerInSet(HashSet set, HashSet
possibleTargets) {
+ double max=-1.0;
+ PeerNode theMan=null;
+
+ Iterator i=set.iterator();
+ while (i.hasNext()) {
+ PeerNode p=(PeerNode)i.next();
+ double value=getPeerNodeConnectedness(p,
possibleTargets);
+ if (value>max) {
+ max=value;
+ theMan=p;
+ }
+ }
+
+ return theMan;
+ }
+
+ // Return a double between [0.0-1.0] somehow indicating how
"wellconnected" this peer is to all the peers in possibleTargets.
+ private double getPeerNodeConnectedness(PeerNode p, HashSet
possibleTargets) {
+ double retval=1.0;
+ double totalLossFactor=1.0/possibleTargets.size();
+ Iterator i=possibleTargets.iterator();
+ while (i.hasNext()) {
+ PeerNode target=(PeerNode)i.next();
+ PingRecord record=getPingRecord(p, target);
+ double pingAverage=record.average.currentValue();
+ if (pingAverage<totalLossFactor)
+ retval*=totalLossFactor;
+ else
+ retval*=pingAverage;
+ }
+ return retval;
+ }
+
+ /*
+ * Returns the set of peers which appear to be reasonably connected to
'thisPeer' and as a
+ * side effect removes those peers from the set passed in.
+ */
+ private List xferConnectedPeerSetFor(PeerNode thisPeer, HashSet
fromOthers) {
+ //FIXME: This algorithm needs to be thought about! Maybe some
hard thresholds.
+ // What recently-connected, peers who only have one or
two pings so far?
+ /*
+ Idea: Right now thisPeer is in a network group by itself, but
we know that it is the
+ best connected peer, so now we just need to find it's
peers. In this implementation
+ A peer belongs to this newly forming network group if it
is at least as connected to
+ the new forming group as the first peer is connected to
the original group.
+ Why? I don't know...
+ */
+ List currentGroup=new ArrayList();
+ currentGroup.add(thisPeer);
+ //HashSet remainder=others.clone();
+ HashSet remainder=fromOthers;
+ double goodConnectivity=getSetwisePingAverage(thisPeer,
fromOthers);
+ while (true) {
+ //Note that, because of the size, this might be low.
+ PeerNode
bestOther=findBestSetwisePingAverage(remainder, currentGroup);
+ if (cheat_findBestSetwisePingAverage_best >=
goodConnectivity) {
+ remainder.remove(bestOther);
+ currentGroup.add(bestOther);
+ } else {
+ break;
+ }
+ }
+ //Exception! If there is only one left in fromOthers and we
have at least a 25% ping average make them be in the same network. This
probably means our algorithim is too picky (spliting up into too many groups).
+ if (currentGroup.size()==1 && fromOthers.size()==1) {
+ PeerNode
onlyLeft=(PeerNode)fromOthers.iterator().next();
+ double average1=getPingRecord(onlyLeft,
thisPeer).average.currentValue();
+ double average2=getPingRecord(thisPeer,
onlyLeft).average.currentValue();
+ if (0.5*average1+0.5*average2 > 0.25) {
+ Logger.normal(this, "combine the dregs:
"+thisPeer+"/"+fromOthers);
+ fromOthers.remove(onlyLeft);
+ currentGroup.add(onlyLeft);
+ }
+ }
+ return currentGroup;
+ }
+
+ private double getSetwisePingAverage(PeerNode thisPeer, Collection
toThesePeers) {
+ Iterator i=toThesePeers.iterator();
+ double accum=0.0;
+ while (i.hasNext()) {
+ PeerNode other=(PeerNode)i.next();
+ accum+=getPingRecord(thisPeer,
other).average.currentValue();
+ }
+ return accum/toThesePeers.size();
+ }
+
+ private PeerNode findBestSetwisePingAverage(HashSet ofThese, Collection
towardsThese) {
+ PeerNode retval=null;
+ double best=-1.0;
+ Iterator i=towardsThese.iterator();
+ while (i.hasNext()) {
+ PeerNode thisOne=(PeerNode)i.next();
+ double average=getSetwisePingAverage(thisOne,
towardsThese);
+ if (average>best) {
+ retval=thisOne;
+ best=average;
+ }
+ }
+ cheat_findBestSetwisePingAverage_best=best;
+ return retval;
+ }
+
+ private double cheat_findBestSetwisePingAverage_best;
+
+ boolean inTransition=false;
+ Object dontStartPlease=new Object();
+
public void onPeerNodeChangedNetworkID(PeerNode p) {
/*
If the network group we assigned to them is (unstable?)...
that is; we would have made a
@@ -569,27 +736,156 @@
of peer-secretpinging/and network-id-reckoning. Note that we
must still not clobber priorities
so...
+ //do nothing if inTransition;
//do nothing on:
p.getNetGroup().disallowedIds.contains(p.getNetID());
//do nothing on: allAssignedNetGroups.contains(p.getNetID());
There is a minor race condition here that between updates we
might improperly favor the first
peer to notify us of a new network id, but this will be
authoritatively clobbered next round.
*/
+ synchronized (dontStartPlease) {
+ if (inTransition)
+ return;
+ //Networks are listed in order of priority, generally
the biggest one should be first.
+ //The forbidden ids is already set in this way, but if
we decide that one group needs to use the id of a lesser group, we must tell
the other group to use a different one; i.e. realign all the previous id's.
+ boolean haveFoundIt=false;
+ PeerNetworkGroup mine=p.networkGroup;
+ Iterator i=networkGroups.iterator();
+ HashSet nowTakenIds=new HashSet();
+ while (i.hasNext()) {
+ PeerNetworkGroup png=(PeerNetworkGroup)i.next();
+ if (png.equals(mine)) {
+ haveFoundIt=true;
+ //should be the same:
png.setForbiddenIds(nowTakenIds);
+ int oldId=png.networkid;
+ int newId=png.getConsensus();
+ if (oldId==newId) {
+ //Maybe they agree with us,
maybe not; but it doesn't change our view of the group.
+ return;
+ } else {
+ if (png.recentlyAssigned()) {
+ //In order to keep us
from thrashing; e.g. two peers each see each other as in the same
+ //group and keep
swapping... we are going to ignore this change for now.
+ return;
+ } else {
+
png.assignNetworkId(newId);
+ }
+ }
+ //to continue means to realign all the
remaining forbidden ids.
+ nowTakenIds.add(new Integer(newId));
+ } else if (haveFoundIt) {
+ //lower priority group, it may need to
be reset.
+ //???: Should we take this oportunity
to always re-examine the consensus? This is a callback, so let's not.
+ png.setForbiddenIds(nowTakenIds);
+ int oldId=png.networkid;
+ int newId=oldId;
+ if (nowTakenIds.contains(new
Integer(oldId))) {
+ newId=png.getConsensus();
+ png.assignNetworkId(newId);
+ }
+ nowTakenIds.add(new Integer(newId));
+ } else {
+ //higher priority group, remember it's
id.
+ nowTakenIds.add(new
Integer(png.networkid));
+ }
+ }
+ }
}
/**
A list of peers that we have assigned a network id to, and some logic
as to why.
*/
- private static class PeerNetworkGroup {
+ public static class PeerNetworkGroup {
List members;
- int networkid;
+ int networkid=NO_NETWORKID;
boolean ourGroup;
+ HashSet forbiddenIds;
+ long lastAssign;
+ /*
+ Returns the group consensus. If no peer in this group has
advertised an id, then the last-assigned id is returned.
+ */
int getConsensus() {
- //mod(peers[].getNetworkID() +
ourGroup?ourID:NO_NETWORKID )
- return NO_NETWORKID;
+ HashMap h=new HashMap();
+ Integer lastId=new Integer(networkid);
+ synchronized (this) {
+ Iterator i=members.iterator();
+ while (i.hasNext()) {
+ PeerNode p=(PeerNode)i.next();
+ Integer id=new
Integer(p.providedNetworkID);
+ //Reject the advertized id which
conflicts with our pre-determined boundaries (which can change)
+ if (forbiddenIds.contains(id))
+ continue;
+ if (id.intValue()==NO_NETWORKID)
+ continue;
+ Integer count=(Integer)h.get(id);
+ if (count==null)
+ count=new Integer(1);
+ else
+ count=new
Integer(count.intValue()+1);
+ h.put(id, count);
+ lastId=id;
+ }
+ //Should we include ourselves in the count?
Probably not, as we generally determine our network id on consensus.
+ //If there is only one option, it is most
likely NO_NETWORKID anyway; or everyone agrees?!
+ if (h.size()<=1)
+ return lastId.intValue();
+ int maxId=networkid;
+ int maxCount=0;
+ Iterator entries=h.entrySet().iterator();
+ while (entries.hasNext()) {
+ Map.Entry e=(Map.Entry)entries.next();
+ int id=((Integer)e.getKey()).intValue();
+ int
count=((Integer)e.getValue()).intValue();
+ if (count>maxCount) {
+ maxCount=count;
+ maxId=id;
+ }
+ }
+ return maxId;
+ }
}
+ void assignNetworkId(int id) {
+ synchronized (this) {
+ this.lastAssign=System.currentTimeMillis();
+ this.networkid=id;
+ Iterator i=members.iterator();
+ while (i.hasNext()) {
+ PeerNode p=(PeerNode)i.next();
+ p.assignedNetworkID=id;
+ p.networkGroup=this;
+ try {
+ p.sendFNPNetworkID();
+ } catch (NotConnectedException e) {
+ Logger.normal(this,
"disconnected on network reassignment");
+ }
+ }
+ }
+ }
+ /*
+ makes a copy of the given set of forbidden ids
+ */
+ void setForbiddenIds(HashSet a) {
+ synchronized (this) {
+ forbiddenIds=new HashSet(a);
+ }
+ }
+ /*
+ caveat, holds onto original list
+ */
+ void setMembers(List a) {
+ synchronized (this) {
+ //more correct to copy, but presently
unneccesary.
+ members=a;
+ }
+ }
+ boolean recentlyAssigned() {
+ return (System.currentTimeMillis()-lastAssign) <
BETWEEN_PEERS;
+ }
}
+ //List of PeerNetworkGroups ordered by priority
+ List networkGroups=new ArrayList();
+
//or zero if we don't know yet
public int ourNetworkId = NO_NETWORKID;
}
\ No newline at end of file
Modified: trunk/freenet/src/freenet/node/PeerNode.java
===================================================================
--- trunk/freenet/src/freenet/node/PeerNode.java 2008-02-08 23:41:38 UTC
(rev 17726)
+++ trunk/freenet/src/freenet/node/PeerNode.java 2008-02-09 00:19:38 UTC
(rev 17727)
@@ -3564,11 +3564,17 @@
int assignedNetworkID;
int providedNetworkID;
+ NetworkIDManager.PeerNetworkGroup networkGroup;
void handleFNPNetworkID(Message m) {
int got=m.getInt(DMT.UID);
if (logMINOR) Logger.minor(this, "now peer thinks he is in
network "+got);
- providedNetworkID=got;
+ if (providedNetworkID!=got && assignedNetworkID!=got) {
+ providedNetworkID=got;
+ node.netid.onPeerNodeChangedNetworkID(this);
+ } else {
+ providedNetworkID=got;
+ }
}
void sendFNPNetworkID() throws NotConnectedException {