Files: 3eb1b173c39d9c87c4c84a786a93eb2059417ac2 / lib / gossip-with-slow-rollout / schedule.js
5570 bytesRaw
1 | var nonPrivate = require('non-private-ip') |
2 | var ip = require('ip') |
3 | var onWakeup = require('on-wakeup') |
4 | var onNetwork = require('on-change-network') |
5 | |
6 | var Stats = require('statistics') |
7 | var os = require('os') |
8 | var pull = require('pull-stream') |
9 | var u = require('scuttlebot/lib/util') |
10 | |
11 | function rand(array) { |
12 | return array[~~(Math.random()*array.length)] |
13 | } |
14 | |
15 | function not (fn) { |
16 | return function (e) { return !fn(e) } |
17 | } |
18 | |
19 | function 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 |
28 | function delay (failures, factor, max) { |
29 | return Math.min(Math.pow(2, failures)*factor, max || Infinity) |
30 | } |
31 | |
32 | function maxStateChange (M, e) { |
33 | return Math.max(M, e.stateChange || 0) |
34 | } |
35 | |
36 | function 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 | |
44 | function 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 | |
50 | var isOnline = not(isOffline) |
51 | |
52 | function 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 | |
58 | function isFollowing (e) { |
59 | return e.source === 'self' |
60 | } |
61 | |
62 | function isUnattempted (e) { |
63 | return !e.stateChange |
64 | } |
65 | |
66 | //select peers which have never been successfully connected to yet, |
67 | //but have been tried. |
68 | function isInactive (e) { |
69 | return e.stateChange && e.duration.mean == 0 |
70 | } |
71 | |
72 | function 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... |
79 | function isLegacy (peer) { |
80 | return peer.duration.mean > 0 && !exports.isLongterm(peer) |
81 | } |
82 | |
83 | function isConnect (e) { |
84 | return 'connected' === e.state || 'connecting' === e.state |
85 | } |
86 | |
87 | //sort oldest to newest then take first n |
88 | function 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 | |
94 | function 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 | |
108 | var schedule = exports = module.exports = |
109 | function (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 | |
205 | exports.isUnattempted = isUnattempted |
206 | exports.isInactive = isInactive |
207 | exports.isLongterm = isLongterm |
208 | exports.isFollowing = isFollowing |
209 | exports.isLegacy = isLegacy |
210 | exports.isLocal = isLocal |
211 | exports.isConnectedOrConnecting = isConnect |
212 | exports.select = select |
213 |
Built with git-ssb-web