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
//! The bot half of this dog: slash commands, the embed they answer with,
//! and the gateway connection that dispatches both.
//!
//! [`embed`] renders one sheep as a [`serenity`] embed with its action
//! buttons; [`command`] is the contract every slash command implements and
//! the state they all read; [`commands`] is where the concrete commands
//! live; [`interaction`] wires an incoming gateway interaction to one of
//! them; and [`run`] brings the gateway itself up. [`channel`] and
//! [`monitor`] are the live monitor: one message per sheep in a channel of
//! its own, edited in place, with [`channel::Board`] as the seam the
//! network sits behind. [`monitor`] is three files, the engine plus
//! [`monitor::refresh`] for the task that drives it and
//! [`monitor::watch`] for what a bus event asks of it. [`crate::stream`] is a
//! separate connection to Discord, over REST alone, for the log-streaming
//! half of this dog; nothing in this module touches it, and nothing in
//! [`crate::stream`] touches a gateway.
use Arc;
use ;
use crate::;
/// How long a failed gateway connection attempt waits before the next one.
///
/// The same order as the run loop's own [`crate::run::RECHECK_INTERVAL`]
/// for the
/// streaming side, and for the same reason: short enough that an operator
/// watching this dog's own output is not left wondering for long, long
/// enough not to hammer Discord's own gateway on a token or network
/// problem that will not resolve itself between one attempt and the next.
const GATEWAY_RETRY_INTERVAL: Duration = from_secs;
/// Bring the gateway up, and keep it up, until `stop` resolves.
///
/// A failed or dropped gateway connection is printed and retried on the
/// next interval, on the same principle [`crate::run::run`] already holds
/// for the streaming side: nothing here is fatal except `stop`
/// resolving, because the shepherd restarting underneath this dog, or
/// Discord's own gateway dropping a shard, is ordinary rather than
/// exceptional, and this half of the dog exiting for it would take the
/// streaming half down with the whole process for no reason of its own.
///
/// `state` and `config` are cloned into a fresh [`interaction::Handler`]
/// on every attempt: every field either carries is an `Arc`, so a
/// reconnect after a dropped shard shares the same shepherd session,
/// resolved config, and monitor the last attempt used rather than losing
/// any of it.
///
/// Never returns an error itself: a failed attempt is printed and retried
/// rather than propagated, on the reasoning above. [`run_once`] is the one
/// place a single attempt's own failure surfaces, for this function to
/// print.
/// The stderr line printed when one gateway attempt ended.
///
/// A function rather than an inline `eprintln!`, the same rule
/// `main` and the run loop follow: a person-facing string a test cannot
/// reach is a string the dash sweep cannot check.
pub async
/// One gateway connection attempt: build a client and run it until it
/// returns on its own, or until `run`'s own outer `select!` above drops
/// this whole future because `stop` resolved first.
///
/// There used to be a second mechanism here: a task spawned to wait on a
/// clone of `stop` and call `shard_manager.shutdown_all()`, closing the
/// websocket properly before this attempt ended, with `shutdown.abort()`
/// cleaning that task up once `client.start()` returned. It never ran.
/// `run`'s `select!` is `biased` with `stop.wait()` listed first, so the
/// instant `stop` resolves that branch wins immediately and this whole
/// function's future, the shutdown task included, is dropped rather than
/// polled again; `shutdown.abort()` only ran after `client.start()`
/// returned on its own, which a stop never let happen. Measured: ctrl-c
/// returns in about 0.02 seconds, far too fast for a websocket close
/// handshake to have taken place, so the graceful path was dead code that
/// looked alive.
///
/// The connection is simply dropped instead. Discord treats a dropped
/// gateway connection the same as one closed properly, and the process is
/// exiting either way, so nothing is lost by not closing it in words. A
/// real graceful close would mean giving up the race above that makes
/// ctrl-c prompt: `run_once` would have to keep running until its own
/// shutdown finished, rather than being torn down the moment `stop.wait()`
/// wins the outer `select!`, which is a different shape of loop than
/// `run`'s.
/// The error is boxed for the reason [`command::register`]'s is: a bare
/// `serenity::Error` is 136 bytes, and [`crate::error::Error`] already
/// keeps its own copy behind a `Box`. `gateway_attempt_ended_message`
/// still takes a `&serenity::Error`, since a `&Box<serenity::Error>`
/// derefs to one at the call.
async