git ssb

0+

alanz / patchwork



forked from Matt McKegg / patchwork

Tree: 650fccfe928ae39c6023e3e2af686943028bb9af

Files: 650fccfe928ae39c6023e3e2af686943028bb9af / lib / gossip-with-slow-rollout / schedule.js

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

Built with git-ssb-web