-
Notifications
You must be signed in to change notification settings - Fork 73
Expand file tree
/
Copy pathtutorial_5_connmanager.nim
More file actions
165 lines (142 loc) · 6.95 KB
/
Copy pathtutorial_5_connmanager.nim
File metadata and controls
165 lines (142 loc) · 6.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
{.used.}
## # Connection Manager Configuration
##
## This tutorial demonstrates how to configure the connection
## manager through `SwitchBuilder` as an API showcase.
## The connection manager controls how many simultaneous connections a switch accepts,
## and can optionally trim low-scoring peers via watermark logic.
##
## You'll find all configuration options in the `withMaxConnections`,
## `withMaxInOut`, and `withWatermarkPolicy` builder methods.
import chronos, strformat
import libp2p
## Helper used to create `SwitchBuilder` with basic options shared in all scenarios.
proc createBaseBuilder(): SwitchBuilder =
return SwitchBuilder
.new()
.withRng(newRng())
.withAddress(MultiAddress.init("/ip4/127.0.0.1/tcp/0").tryGet())
.withTcpTransport()
.withMplex()
.withNoise()
proc connectPeer(switch: Switch, name: string, description: string) {.async.} =
await switch.start()
let peer = createBaseBuilder().build()
await peer.start()
await peer.connect(switch.peerInfo.peerId, switch.peerInfo.addrs)
let count = switch.connectedPeers(In).len
echo fmt"{name:<60} connected = {count}", "\t", description
await allFutures(switch.stop(), peer.stop())
## The seven configurations
##
## Each block below creates a switch with a different connection-manager
## configuration, starts it, connects one peer, prints the connected-peer
## count, then shuts everything down cleanly.
proc main() {.async.} =
## ### 1. `.build()` — default shared limit at 50
##
## When no connection-limit option is supplied the switch uses a default,
## **shared** semaphore capped at `MaxConnections` (50). Both incoming and outgoing
## connections draw from the same pool, so the total can never exceed 50.
block:
let switch = createBaseBuilder().build()
await connectPeer(switch, "1. .build()", "(shared limit: 50)")
## ### 2. `.withMaxConnections(100)` — raise the shared cap
##
## Both incoming and outgoing connections share one semaphore of size 100.
## The 101st connection attempt raises error on the dialing side (outgoing) or
## blocks until a slot is released (incoming).
block:
let switch = createBaseBuilder().withMaxConnections(100).build()
await connectPeer(switch, "2. .withMaxConnections(100)", "(shared limit: 100)")
## ### 3. `.withMaxInOut(30, 20)` — independent per-direction caps
##
## Two separate semaphores are created: 30 slots for **incoming**
## connections and 20 slots for **outgoing** dials. Outbound traffic
## can never exhaust the inbound pool and vice-versa, giving finer
## control over how bandwidth is allocated.
block:
let switch = createBaseBuilder().withMaxInOut(30, 20).build()
await connectPeer(
switch, "3. .withMaxInOut(30, 20)", "(in-limit: 30, out-limit: 20)"
)
## ### 4. `.withWatermarkPolicy(10, 20)` — soft trimming, no hard cap
##
## No semaphore is created — the switch **never blocks** an incoming
## connection based on count alone. Instead, once the connected-peer count
## exceeds the **high-water mark** (20), the connection manager
## asynchronously trims down to the **low-water mark** (10) by closing
## the lowest-scoring peers. Peers within the grace period (default 1 min)
## and protected peers are skipped during trimming.
block:
let switch = createBaseBuilder().withWatermarkPolicy(10, 20).build()
await connectPeer(
switch, "4. .withWatermarkPolicy(10, 20)",
"(no hard cap; trims to 10 once 20 are connected)",
)
## ### 5. `.withWatermarkPolicy(10, 20, gracePeriod = 30.seconds, silencePeriod = 5.seconds)` — custom timing
##
## `gracePeriod` protects newly connected peers from being trimmed: any peer
## that connected less than `gracePeriod` ago is skipped when the connection
## manager prunes down to `lowWater`. Shortening it from the default 1 minute
## to 30 seconds makes the trimmer willing to drop peers sooner after they connect.
##
## `silencePeriod` is a cooldown between successive trim cycles: once a trim
## run finishes, the next cycle cannot start until `silencePeriod` has elapsed.
## Reducing it from the default 10 seconds to 5 seconds lets the connection
## manager react faster when many peers connect in quick succession.
##
## Tune these two durations together to balance connection churn against
## responsiveness: a long grace period preserves recently opened connections
## longer, while a short silence period allows more frequent pruning passes.
block:
let switch = createBaseBuilder()
.withWatermarkPolicy(10, 20, gracePeriod = 30.seconds, silencePeriod = 5.seconds)
.build()
await connectPeer(
switch, "5. .withWatermarkPolicy(gracePeriod=30s, silencePeriod=5s)",
"(trims at 20; grace 30 s; silence 5 s)",
)
## ### 6. `.withWatermarkPolicy(10, 20).withMaxConnections(30)` — hard cap + trimming
##
## A semaphore enforces an absolute ceiling of 30 connections while
## watermark trimming keeps the active peer count near 10 long before that
## ceiling is ever reached. Both guards operate simultaneously: the
## semaphore blocks new connections at the limit, and the trimmer prunes
## existing ones once the high-water mark is hit.
block:
let switch =
createBaseBuilder().withWatermarkPolicy(10, 20).withMaxConnections(30).build()
await connectPeer(
switch, "6. .withWatermarkPolicy(10,20).withMaxConnections(30)",
"(hard cap: 30; trims at 20)",
)
## ### 7. `.withWatermarkPolicy(10, 20).withMaxInOut(30, 20)` — per-direction caps + trimming
##
## The most granular configuration: two independent semaphores (30 incoming,
## 20 outgoing) combined with watermark trimming. New connections are
## blocked by the per-direction caps, and existing connections are pruned
## asynchronously once the total peer count exceeds 20.
block:
let switch =
createBaseBuilder().withWatermarkPolicy(10, 20).withMaxInOut(30, 20).build()
await connectPeer(
switch, "7. .withWatermarkPolicy(10,20).withMaxInOut(30,20)",
"(in-limit: 30, out-limit: 20; trims at 20)",
)
waitFor(main())
## Running this program with `nim c -p:../ -d:chronicles_log_level=error -r tutorial_5_connmanager.nim` produces
## one line per scenario, each showing `connected = 1`, confirming that each
## configuration accepts connections normally under its limit.
##
## ## Summary
##
## | Builder call | Effect |
## |---|---|
## | `.build()` | Shared limit at 50 (default `MaxConnections`) |
## | `.withMaxConnections(100)` | Shared limit at 100 |
## | `.withMaxInOut(30, 20)` | Separate limits: 30 incoming / 20 outgoing |
## | `.withWatermarkPolicy(10, 20)` | No hard cap; trims to 10 once 20 connected |
## | `.withWatermarkPolicy(10, 20, gracePeriod = 30.seconds, silencePeriod = 5.seconds)` | Custom trim timing: 30s grace, 5s cooldown |
## | `.withWatermarkPolicy(10, 20).withMaxConnections(30)` | Hard cap at 30 + trim at 20 |
## | `.withWatermarkPolicy(10, 20).withMaxInOut(30, 20)` | Separate limits + trim at 20 |