fix(scripts): refuse to reuse a backend that is serving the previous database (#257) #274
@@ -65,6 +65,11 @@ $ErrorActionPreference = 'Stop'
|
|||||||
$RepoRoot = Split-Path -Parent $PSScriptRoot
|
$RepoRoot = Split-Path -Parent $PSScriptRoot
|
||||||
$StateDir = Join-Path $RepoRoot '.local'
|
$StateDir = Join-Path $RepoRoot '.local'
|
||||||
$PidFile = Join-Path $StateDir 'pids.json'
|
$PidFile = Join-Path $StateDir 'pids.json'
|
||||||
|
|
||||||
|
# Whether this run is starting from an empty database. Set below, and read by
|
||||||
|
# Start-Backend, which must refuse to reuse a backend that is still serving the
|
||||||
|
# previous one. See #257.
|
||||||
|
$script:DatabaseIsNew = $false
|
||||||
# Which database this run uses. Everything downstream reads these four, so the
|
# Which database this run uses. Everything downstream reads these four, so the
|
||||||
# choice is made once here rather than branched at each use.
|
# choice is made once here rather than branched at each use.
|
||||||
if ($E2eDb) {
|
if ($E2eDb) {
|
||||||
@@ -184,14 +189,22 @@ function Start-Database {
|
|||||||
Write-Step 'Removing the existing database (-Fresh)'
|
Write-Step 'Removing the existing database (-Fresh)'
|
||||||
docker rm -f $Container *>$null
|
docker rm -f $Container *>$null
|
||||||
Write-Good 'removed'
|
Write-Good 'removed'
|
||||||
|
$script:DatabaseIsNew = $true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# The e2e container stores its data on tmpfs, so it comes up empty whether it
|
||||||
|
# is created or merely restarted. There is no case where it carries anything
|
||||||
|
# over, which is the point of it (#186) — and it means a backend from a
|
||||||
|
# previous run is always stale against it.
|
||||||
|
if ($E2eDb) { $script:DatabaseIsNew = $true }
|
||||||
|
|
||||||
$existing = (docker ps -a --filter "name=^/$Container$" --format '{{.Names}}')
|
$existing = (docker ps -a --filter "name=^/$Container$" --format '{{.Names}}')
|
||||||
if ($existing -eq $Container) {
|
if ($existing -eq $Container) {
|
||||||
Write-Step "Reusing the database container"
|
Write-Step "Reusing the database container"
|
||||||
docker start $Container *>$null
|
docker start $Container *>$null
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
$script:DatabaseIsNew = $true
|
||||||
Write-Step "Creating the database container on port $DbPort"
|
Write-Step "Creating the database container on port $DbPort"
|
||||||
# tmpfs for the e2e database only: its contents are worthless the
|
# tmpfs for the e2e database only: its contents are worthless the
|
||||||
# moment the run ends, and storing nothing is what makes it start empty
|
# moment the run ends, and storing nothing is what makes it start empty
|
||||||
@@ -269,6 +282,41 @@ function Start-Backend {
|
|||||||
finally { Pop-Location }
|
finally { Pop-Location }
|
||||||
|
|
||||||
if (Test-Listening -Port $ApiPort) {
|
if (Test-Listening -Port $ApiPort) {
|
||||||
|
# Leaving a stranger's backend alone is only safe when the database has
|
||||||
|
# not just changed underneath it. When it has, that process is serving a
|
||||||
|
# database it no longer owns, and its in-memory state describes rows that
|
||||||
|
# no longer exist.
|
||||||
|
#
|
||||||
|
# That is not theoretical. The rate limiter keys on customer id and keeps
|
||||||
|
# its buckets in memory for an hour; a recreated database restarts ids at
|
||||||
|
# 1, so a new customer inherits a previous run's spent allowance. It
|
||||||
|
# reproduced exactly the intermittent resend-verification failure in #257
|
||||||
|
# — three refusal toasts where one was expected — and the control (same
|
||||||
|
# id restart, restarted backend) passed. Whatever else is stale, a
|
||||||
|
# process serving the wrong database gives wrong answers confidently,
|
||||||
|
# which is the worst kind.
|
||||||
|
#
|
||||||
|
# So this refuses rather than continuing, and says what to do. A run that
|
||||||
|
# stops loudly is recoverable; one that quietly tests the wrong thing is
|
||||||
|
# what cost two wrong measurements in this project already.
|
||||||
|
$tracked = Get-TrackedProcesses
|
||||||
|
$ours = $tracked.ContainsKey('backend') -and
|
||||||
|
(Get-Process -Id $tracked['backend'] -ErrorAction SilentlyContinue)
|
||||||
|
|
||||||
|
if ($script:DatabaseIsNew -and -not $ours) {
|
||||||
|
throw @"
|
||||||
|
Something is already listening on $ApiPort, and this run just created a new database.
|
||||||
|
|
||||||
|
That process is serving the previous database. Its in-memory state — rate-limit
|
||||||
|
buckets keyed on customer id, most obviously — describes rows that no longer
|
||||||
|
exist, and recycled ids will inherit them (#257).
|
||||||
|
|
||||||
|
Stop it and run this again:
|
||||||
|
|
||||||
|
.\scripts\start-local.ps1 -Stop
|
||||||
|
"@
|
||||||
|
}
|
||||||
|
|
||||||
Write-Note "something is already listening on $ApiPort; leaving it alone"
|
Write-Note "something is already listening on $ApiPort; leaving it alone"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user