git ssb

10+

Matt McKegg / patchwork



Tree: 3eb1b173c39d9c87c4c84a786a93eb2059417ac2

Files: 3eb1b173c39d9c87c4c84a786a93eb2059417ac2 / lib / gossip-with-slow-rollout / schedule.js

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

Built with git-ssb-web