2 Commits
Author SHA1 Message Date
clawbot cbe686158c Exit with the shutdown's code and wait for image processing (closes #86)
check / check (push) Successful in 6m54s
fx alone handles SIGINT and SIGTERM; the server's own handler, which
only cancelled a context that fx's stop did not wait for, is gone.
fx's Run exits with the shutdown's code: the one a shutdown request
carries, 0 for a signal, 1 when the app fails to start or stop.
A listen error asks fx to shut down with exit code 1. A Sentry DSN that
cannot be used fails the server's start hook, so fx stops what had
already started. The server's stop hook stops the HTTP server, then
waits for the images still being processed, both within
ShutdownTimeout; images still being processed after that are logged and
fail the stop, so the exit code is 1.

Model: opus-5-5
2026-10-04 00:26:46 +00:00
clawbot eec5c195fb Test shutdown exit codes, Sentry startup failure and the processing wait
These tests fail until the change that follows: WaitForProcessing does
not exist yet, the server has no shutdowner, and a Sentry DSN that
cannot be used exits the process from a goroutine instead of failing
the server's start hook.

A listen error must ask fx to shut down with exit code 1.
WaitForProcessing must wait for an image being processed and report it
when its context ends first.

Model: opus-5-5
2026-10-04 00:25:44 +00:00
6 changed files with 16 additions and 39 deletions
+5 -11
View File
@@ -18,14 +18,9 @@ COPY . .
# Tells script/lint it is inside a container, so it runs the linter. # Tells script/lint it is inside a container, so it runs the linter.
ENV container=docker ENV container=docker
# Run formatting check and linter. script/cibuild and script/docker pass # Run formatting check and linter
# a new CHECK_EPOCH on every run, and each check step names it in its RUN make fmt-check
# command, so a new value reruns the step instead of reusing a cached RUN make lint
# success that checked nothing. A plain `docker build .` leaves it empty
# and reuses the check steps only for an identical build context.
ARG CHECK_EPOCH
RUN echo "check epoch: ${CHECK_EPOCH}" && make fmt-check
RUN echo "check epoch: ${CHECK_EPOCH}" && make lint
# Build stage # Build stage
# golang:1.25.4-alpine, 2026-02-25 # golang:1.25.4-alpine, 2026-02-25
@@ -44,9 +39,8 @@ RUN script/bootstrap
# Copy source code # Copy source code
COPY . . COPY . .
# Run tests; a new CHECK_EPOCH reruns them, as in the lint stage. # Run tests
ARG CHECK_EPOCH RUN make test
RUN echo "check epoch: ${CHECK_EPOCH}" && make test
# VERSION is declared here, not earlier: a new value reruns only the # VERSION is declared here, not earlier: a new value reruns only the
# build, not script/bootstrap or the tests. Given none, the version is # build, not script/bootstrap or the tests. Given none, the version is
+2 -3
View File
@@ -353,9 +353,8 @@ them. We provide:
- `script/check` — run test, lint, and fmt-check - `script/check` — run test, lint, and fmt-check
- `script/docker` — build the Docker image tagged via `script/projectname` - `script/docker` — build the Docker image tagged via `script/projectname`
- `script/docker-smoke` — build the image, start it, wait for it to be healthy - `script/docker-smoke` — build the image, start it, wait for it to be healthy
- `script/cibuild` — CI entrypoint: `docker build .` with a new - `script/cibuild` — CI entrypoint: `docker build .` (the Dockerfile
`CHECK_EPOCH` on every run, so the Dockerfile's checks run instead of runs the checks, so a green build implies a green repo)
coming from the build cache, and a green run implies a green repo
- `script/precommit` — pre-commit checks (`go mod tidy` guard, then - `script/precommit` — pre-commit checks (`go mod tidy` guard, then
`script/check`) `script/check`)
- `script/install-precommit` — install the git pre-commit hook that - `script/install-precommit` — install the git pre-commit hook that
-9
View File
@@ -40,15 +40,6 @@ P2: security: referer blacklist
used fails startup, so the stop hooks of what had already started run, used fails startup, so the stop hooks of what had already started run,
instead of exiting the process from a goroutine; the eviction loop is left to instead of exiting the process from a goroutine; the eviction loop is left to
#102. #102.
- 2026-10-03 every `script/cibuild` and `script/docker` run executes the checks
(closes #101): the `Dockerfile` declares `CHECK_EPOCH` above `make fmt-check`
and `make lint` in the lint stage and above `make test` in the build stage,
and each of those steps names it in its command; both scripts pass a new value
on every run, so Docker runs the checks instead of reusing cached results,
while the `script/bootstrap` steps stay cached; a plain `docker build .` still
works, leaves it empty, and reuses the check steps only for an identical build
context; the `script/cibuild` comment and `README.md` no longer say that any
successful build implies a green repo.
- 2026-09-29 share concurrent misses (closes #65): requests that miss the same - 2026-09-29 share concurrent misses (closes #65): requests that miss the same
variant at once (the same cache key, so quality and fit included) share one variant at once (the same cache key, so quality and fit included) share one
upstream fetch or cached source read and one transcode through upstream fetch or cached source read and one transcode through
+2 -2
View File
@@ -333,8 +333,8 @@ func FormatToMIME(format Format) string {
// WaitForProcessing waits until no image is being processed, or until ctx // WaitForProcessing waits until no image is being processed, or until ctx
// ends, and returns how many images were still being processed then. It // ends, and returns how many images were still being processed then. It
// waits by taking each slot in processingSemaphore as it frees up until it // waits by taking every slot in processingSemaphore as it frees up, so no
// holds them all, or until ctx ends, then gives back the slots it took. // new image starts meanwhile, and gives them all back before it returns.
func (p *ImageProcessor) WaitForProcessing(ctx context.Context) int { func (p *ImageProcessor) WaitForProcessing(ctx context.Context) int {
taken := 0 taken := 0
+4 -7
View File
@@ -1,18 +1,15 @@
#!/bin/sh #!/bin/sh
# script/cibuild: run the CI build. The Dockerfile runs the checks # script/cibuild: run the CI build. The Dockerfile runs the checks
# (make fmt-check, lint, test) as build steps. This script passes a new # (make fmt-check, lint, test), so a successful build implies a green
# CHECK_EPOCH on every run, so Docker runs those steps instead of # repo. Generic: needs no adaptation. The Gitea workflow runs this on
# reusing cached results: a successful run means the checks ran and # push.
# passed on this tree. Generic: needs no adaptation. The Gitea workflow
# runs this on push.
set -eu set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() { main() {
cd "$ROOT" cd "$ROOT"
epoch="$(date +%s)$$" docker build .
docker build --build-arg CHECK_EPOCH="$epoch" .
} }
main "$@" main "$@"
+3 -7
View File
@@ -1,9 +1,7 @@
#!/bin/sh #!/bin/sh
# script/docker: build the Docker image tagged with the project name. # script/docker: build the Docker image tagged with the project name.
# Identical in all repos; the tag comes from script/projectname. Like # Identical in all repos; the tag comes from script/projectname.
# script/cibuild, it passes a new CHECK_EPOCH, so the build runs the # Generic: needs no adaptation.
# checks instead of reusing cached results. Generic: needs no
# adaptation.
set -eu set -eu
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
@@ -11,9 +9,7 @@ ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
main() { main() {
cd "$ROOT" cd "$ROOT"
epoch="$(date +%s)$$" docker build -t "$("$SCRIPT_DIR/projectname")" .
docker build --build-arg CHECK_EPOCH="$epoch" \
-t "$("$SCRIPT_DIR/projectname")" .
} }
main "$@" main "$@"