git ssb

4+

Dominic / scuttlebot



Tree: ed7e4997927bef14491b40767dbd64f58f2d246e

Files: ed7e4997927bef14491b40767dbd64f58f2d246e / plugins / gossip / schedule.js

6879 bytesRaw
1'use strict'
2var ip = require('ip')
3var onWakeup = require('on-wakeup')
4var onNetwork = require('on-change-network')
5var hasNetwork = require('has-network')
6
7var pull = require('pull-stream')
8
9function not (fn) {
10 return function (e) { return !fn(e) }
11}
12
13function and () {
14 var args = [].slice.call(arguments)
15 return function (value) {
16 return args.every(function (fn) { return fn.call(null, value) })
17 }
18}
19
20//min delay (delay since last disconnect of most recent peer in unconnected set)
21//unconnected filter delay peer < min delay
22function delay (failures, factor, max) {
23 return Math.min(Math.pow(2, failures)*factor, max || Infinity)
24}
25
26function maxStateChange (M, e) {
27 return Math.max(M, e.stateChange || 0)
28}
29
30function peerNext(peer, opts) {
31 return (peer.stateChange|0) + delay(peer.failure|0, opts.factor, opts.max)
32}
33
34
35//detect if not connected to wifi or other network
36//(i.e. if there is only localhost)
37
38function isOffline (e) {
39 if(ip.isLoopback(e.host) || e.host == 'localhost') return false
40 return !hasNetwork()
41}
42
43var isOnline = not(isOffline)
44
45function isLocal (e) {
46 // don't rely on private ip address, because
47 // cjdns creates fake private ip addresses.
48 // ignore localhost addresses, because sometimes they get broadcast.
49 return !ip.isLoopback(e.host) && ip.isPrivate(e.host) && e.source === 'local'
50}
51
52function isSeed (e) {
53 return e.source === 'seed'
54}
55
56function isFriend (e) {
57 return e.source === 'friends'
58}
59
60function isUnattempted (e) {
61 return !e.stateChange
62}
63
64//select peers which have never been successfully connected to yet,
65//but have been tried.
66function isInactive (e) {
67 return e.stateChange && (!e.duration || e.duration.mean == 0)
68}
69
70function isLongterm (e) {
71 return e.ping && e.ping.rtt && e.ping.rtt.mean > 0
72}
73
74//peers which we can connect to, but are not upgraded.
75//select peers which we can connect to, but are not upgraded to LT.
76//assume any peer is legacy, until we know otherwise...
77function isLegacy (peer) {
78 return peer.duration && (peer.duration && peer.duration.mean > 0) && !exports.isLongterm(peer)
79}
80
81function isConnect (e) {
82 return 'connected' === e.state || 'connecting' === e.state
83}
84
85//sort oldest to newest then take first n
86function earliest(peers, n) {
87 return peers.sort(function (a, b) {
88 return a.stateChange - b.stateChange
89 }).slice(0, Math.max(n, 0))
90}
91
92function select(peers, ts, filter, opts) {
93 if(opts.disable) return []
94 //opts: { quota, groupMin, min, factor, max }
95 var type = peers.filter(filter)
96 var unconnect = type.filter(not(isConnect))
97 var count = Math.max(opts.quota - type.filter(isConnect).length, 0)
98 var min = unconnect.reduce(maxStateChange, 0) + opts.groupMin
99 if(ts < min) return []
100
101 return earliest(unconnect.filter(function (peer) {
102 return peerNext(peer, opts) < ts
103 }), count)
104}
105
106var schedule = exports = module.exports =
107function (gossip, config, server) {
108// return
109 var min = 60e3, hour = 60*60e3, closed = false
110
111 //trigger hard reconnect after suspend or local network changes
112 onWakeup(gossip.reconnect)
113 onNetwork(gossip.reconnect)
114
115 function conf(name, def) {
116 if(config.gossip == null) return def
117 var value = config.gossip[name]
118 return (value === undefined || value === '') ? def : value
119 }
120
121 function connect (peers, ts, name, filter, opts) {
122 opts.group = name
123 var connected = peers.filter(isConnect).filter(filter)
124
125 //disconnect if over quota
126 if(connected.length > opts.quota) {
127 return earliest(connected, connected.length - opts.quota)
128 .forEach(function (peer) {
129 gossip.disconnect(peer)
130 })
131 }
132
133 //will return [] if the quota is full
134 var selected = select(peers, ts, and(filter, isOnline), opts)
135 selected
136 .forEach(function (peer) {
137 gossip.connect(peer)
138 })
139 }
140
141
142 var connecting = false
143 function connections () {
144 if(connecting || closed) return
145 connecting = true
146 var timer = setTimeout(function () {
147 connecting = false
148 var ts = Date.now()
149 var peers = gossip.peers()
150
151 var connected = peers.filter(and(isConnect, not(isLocal), not(isFriend))).length
152
153 var connectedFriends = peers.filter(and(isConnect, isFriend)).length
154
155 if(conf('seed', true))
156 connect(peers, ts, 'seeds', isSeed, {
157 quota: 3, factor: 2e3, max: 10*min, groupMin: 1e3,
158 })
159 return //XXX
160 if(conf('local', true))
161 connect(peers, ts, 'local', isLocal, {
162 quota: 3, factor: 2e3, max: 10*min, groupMin: 1e3,
163 })
164
165 if(conf('global', true)) {
166 // prioritize friends
167 connect(peers, ts, 'friends', and(exports.isFriend, exports.isLongterm), {
168 quota: 2, factor: 10e3, max: 10*min, groupMin: 5e3,
169 })
170
171 if (connectedFriends < 2)
172 connect(peers, ts, 'attemptFriend', and(exports.isFriend, exports.isUnattempted), {
173 min: 0, quota: 1, factor: 0, max: 0, groupMin: 0,
174 })
175
176 connect(peers, ts, 'retryFriends', and(exports.isFriend, exports.isInactive), {
177 min: 0,
178 quota: 3, factor: 60e3, max: 3*60*60e3, groupMin: 5*60e3,
179 })
180
181 // standard longterm peers
182 connect(peers, ts, 'longterm', and(
183 exports.isLongterm,
184 not(exports.isFriend),
185 not(exports.isLocal)
186 ), {
187 quota: 2, factor: 10e3, max: 10*min, groupMin: 5e3,
188 })
189
190 if(!connected)
191 connect(peers, ts, 'attempt', exports.isUnattempted, {
192 min: 0, quota: 1, factor: 0, max: 0, groupMin: 0,
193 })
194
195 //quota, groupMin, min, factor, max
196 connect(peers, ts, 'retry', exports.isInactive, {
197 min: 0,
198 quota: 3, factor: 5*60e3, max: 3*60*60e3, groupMin: 5*50e3,
199 })
200
201 var longterm = peers.filter(isConnect).filter(isLongterm).length
202
203 connect(peers, ts, 'legacy', exports.isLegacy, {
204 quota: 3 - longterm,
205 factor: 5*min, max: 3*hour, groupMin: 5*min,
206 })
207 }
208
209 peers.filter(isConnect).forEach(function (e) {
210 var permanent = exports.isLongterm(e) || exports.isLocal(e)
211 if((!permanent || e.state === 'connecting') && e.stateChange + 10e3 < ts) {
212 gossip.disconnect(e)
213 }
214 })
215
216 }, 100*Math.random())
217 if(timer.unref) timer.unref()
218 }
219
220 pull(
221 gossip.changes(),
222 pull.drain(function (ev) {
223 if(ev.type == 'disconnect')
224 connections()
225 })
226 )
227
228 var int = setInterval(connections, 2e3)
229 if(int.unref) int.unref()
230
231 connections()
232
233 return function onClose () {
234 closed = true
235
236 }
237
238}
239
240exports.isUnattempted = isUnattempted
241exports.isInactive = isInactive
242exports.isLongterm = isLongterm
243exports.isLegacy = isLegacy
244exports.isLocal = isLocal
245exports.isFriend = isFriend
246exports.isConnectedOrConnecting = isConnect
247exports.select = select
248
249

Built with git-ssb-web