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
//! Watching a migration run while it happens.
//!
//! The runner used to be silent: it took the migrate lock, applied every
//! pending file, and returned a `Vec<Migration>` when it was done. On a
//! fresh tenant that is twenty-plus migrations of nothing, and a failure
//! reported only which *tenant* died, never which migration or how many
//! had already landed.
//!
//! An observer fixes that without changing what the runner does. Pass one
//! to [`migrate_pool_with_progress`](super::migrate_pool_with_progress)
//! and it is called as each migration starts and finishes; pass nothing
//! and the behaviour is exactly what it was.
//!
//! ## Observers must not block
//!
//! Events are emitted from **inside the migrate lock** — a PG advisory
//! lock, a MySQL `GET_LOCK`, held for the whole run. Every other process
//! trying to migrate is waiting behind it. An observer that blocks, does
//! synchronous I/O, or sends on a full unbuffered channel extends that
//! hold for everyone.
//!
//! Send on a bounded channel that drops when full, or push onto a queue a
//! separate task drains. Losing a progress event is a cosmetic problem;
//! stalling every migrating pod is not.
//!
//! ```ignore
//! use rustango::migrate::{migrate_pool_with_progress, MigrationEvent};
//!
//! migrate_pool_with_progress(&pool, dir, &|event: MigrationEvent| {
//! if let MigrationEvent::Finished { name, index, total, .. } = &event {
//! println!("[{index}/{total}] {name}");
//! }
//! })
//! .await?;
//! ```
use Duration;
/// Something that happened during a migration run.
///
/// Owned rather than borrowed on purpose: an observer's whole job is
/// usually to hand the event to somewhere else — a channel, a broadcast
/// bus, a run log — and a borrow would tie it to the runner's stack. One
/// allocation per migration is not measurable against the DDL it
/// describes.
/// What the runner did with a migration it reports as finished.
///
/// Three genuinely different things wear the word "applied", and
/// collapsing them is how a watcher comes to believe DDL ran that never
/// did.
/// Receives [`MigrationEvent`]s as a run progresses.
///
/// Implemented for any `Fn(MigrationEvent)`, so the common case is a
/// closure:
///
/// ```ignore
/// migrate_pool_with_progress(&pool, dir, &|e| tx.try_send(e).ok()).await?;
/// ```
///
/// **Implementations must not block** — see the [module
/// docs](self#observers-must-not-block). Events arrive while the
/// migrate lock is held.
/// Emit to an optional observer. `None` is the silent path every
/// pre-existing entry point takes, and compiles to nothing.
pub