name: Nightly stress

on:
  schedule:
    # 03:00 UTC daily.
    - cron: '0 3 * * *'
  workflow_dispatch:
    inputs:
      iterations:
        description: 'Number of stress iterations'
        required: false
        default: '50'
        type: string

env:
  ITERATIONS: ${{ github.event.inputs.iterations || '50' }}

jobs:
  nightly-stress:
    name: PG${{ matrix.pg }} stress
    runs-on: ubuntu-24.04
    timeout-minutes: 90
    # The `asan` leg runs the same scripts inside the ASan-instrumented
    # PostgreSQL that scripts/asan-check.sh builds (ASAN_STRESS=1).
    strategy:
      fail-fast: false
      matrix:
        pg: ['18']
        # `plain` runs without sanitizers — fast feedback on the
        # mixed workload itself.  `asan` runs the same workload
        # under ASAN+UBSAN to catch memory bugs that surface only
        # under churn.
        sanitize: ['plain', 'asan']

    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive

      - name: Install build deps
        run: |
          # PG 17/18 are not in the default Ubuntu repos; add PGDG first.
          sudo apt-get update
          sudo apt-get install -y wget ca-certificates gnupg lsb-release
          wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc \
              | sudo gpg --dearmor -o /usr/share/keyrings/pgdg.gpg
          echo "deb [signed-by=/usr/share/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \
              | sudo tee /etc/apt/sources.list.d/pgdg.list >/dev/null
          sudo apt-get update
          sudo apt-get install -y \
            build-essential autoconf automake libtool gettext autopoint bison flex \
            wget ca-certificates gnupg systemd-coredump gdb \
            postgresql-${{ matrix.pg }} postgresql-server-dev-${{ matrix.pg }}

      - name: Build pg_tre (plain)
        if: matrix.sanitize == 'plain'
        run: |
          export PG_CONFIG=/usr/lib/postgresql/${{ matrix.pg }}/bin/pg_config
          export PATH=/usr/lib/postgresql/${{ matrix.pg }}/bin:$PATH
          make
          sudo env "PATH=$PATH" make install

      - name: Lower ASLR entropy for AddressSanitizer (asan only)
        if: matrix.sanitize == 'asan'
        run: |
          sudo sysctl -w vm.mmap_rnd_bits=28
          sudo sysctl -w kernel.randomize_va_space=0

      - name: Cache ASan PostgreSQL (asan only)
        if: matrix.sanitize == 'asan'
        uses: actions/cache@v4
        with:
          path: ~/pg-asan-18.0
          key: pg-asan-18.0-ubuntu24-${{ hashFiles('scripts/asan-check.sh') }}

      - name: Stress, wal_audit, replication under ASan (asan only)
        if: matrix.sanitize == 'asan'
        # The ASan-instrumented PostgreSQL scripts/asan-check.sh builds (the
        # Sanitizer test job's, same cache key), with pg_tre instrumented the
        # same way.  Preloading an ASan runtime into the stock PGDG server
        # cannot work on this kernel ("Shadow memory range interleaves with
        # an existing memory mapping"), which is why this leg never passed.
        env:
          ASAN_WORK: ${{ runner.temp }}/pg_tre-asan
          ASAN_STRESS: '1'
          ROWS_PER_INSERT: '500'
          PARALLEL_SELECTS: '4'
          DELETE_FRACTION: '5'
          VACUUM_EVERY: '5'
          REINDEX_EVERY: '10'
          RSS_CEILING_MB: '4096'
        run: |
          sudo apt-get install -y --no-install-recommends curl bzip2 locales
          ASAN_PREFIX="$HOME/pg-asan-18.0" bash scripts/asan-check.sh

      - name: Run stress.sh
        if: matrix.sanitize == 'plain'
        env:
          ITERATIONS: ${{ env.ITERATIONS }}
          ROWS_PER_INSERT: '500'
          PARALLEL_SELECTS: '4'
          DELETE_FRACTION: '5'
          VACUUM_EVERY: '5'
          REINDEX_EVERY: '10'
          RSS_CEILING_MB: '4096'
        run: |
          export PATH=/usr/lib/postgresql/${{ matrix.pg }}/bin:$PATH
          export PG_CONFIG=/usr/lib/postgresql/${{ matrix.pg }}/bin/pg_config
          bash test/scripts/stress.sh

      - name: Run wal_audit.sh
        if: matrix.sanitize == 'plain'
        run: |
          export PATH=/usr/lib/postgresql/${{ matrix.pg }}/bin:$PATH
          export PG_CONFIG=/usr/lib/postgresql/${{ matrix.pg }}/bin/pg_config
          bash test/scripts/wal_audit.sh

      - name: Run replication.sh
        if: matrix.sanitize == 'plain'
        run: |
          export PATH=/usr/lib/postgresql/${{ matrix.pg }}/bin:$PATH
          export PG_CONFIG=/usr/lib/postgresql/${{ matrix.pg }}/bin/pg_config
          bash test/scripts/replication.sh

      - name: Run crash-recovery TAP (catalog WAL crash-safety)
        if: matrix.sanitize != 'asan'
        run: |
          export PG_CONFIG=/usr/lib/postgresql/${{ matrix.pg }}/bin/pg_config
          export PATH=/usr/lib/postgresql/${{ matrix.pg }}/bin:$PATH
          perl -MIPC::Run -e1 2>/dev/null || sudo apt-get install -y libipc-run-perl
          # Replicate the env the Makefile 'tap' target sets, but run
          # ONLY crash_recovery.pl (the Phase B1.3 gate) -- the other
          # tap/*.pl are heavyweight and covered elsewhere.
          export PERL5LIB="$($PG_CONFIG --pkglibdir)/pgxs/src/test/perl"
          export PG_REGRESS="$($PG_CONFIG --pkglibdir)/pgxs/src/test/regress/pg_regress"
          export TMPDIR=/tmp/pg_tre_tap_tmp
          mkdir -p "$TMPDIR"
          # crash_recovery.pl drives tre_debug_append_run every batch so
          # catalog-append WAL records are in flight at kill -9, and
          # asserts >= 2 runs survive replay -- the exact scenario that
          # exposed the WILL_INIT bug the B1.3 writer fixes.
          prove -v tap/crash_recovery.pl

      - name: Upload diagnostics on failure
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: stress-${{ matrix.pg }}-${{ matrix.sanitize }}
          path: |
            ${{ runner.temp }}/pg_tre-asan/reports/
            ${{ runner.temp }}/pg_tre-asan/pg_tre_*/primary/logfile
            /tmp/pg_tre_*/primary/logfile
          retention-days: 14

      - name: Notify on failure
        if: failure() && github.event_name == 'schedule'
        run: |
          # Placeholder: the matrix produces an artifact even
          # without an explicit notify step, and Codeberg /
          # GitHub email notifications cover the rest.  When
          # we have a Slack/Discord webhook, post here.
          echo "::warning::Nightly stress failed; see artifacts above"
