From 687087372d2311f46f4b1870f573eb2d5ffc4974 Mon Sep 17 00:00:00 2001 From: slayerjain Date: Mon, 19 Oct 2026 21:28:22 +0530 Subject: [PATCH 1/3] fix(restheart-mongo): flow.sh waits for RESTHeart to answer, not for a deadline record-traffic gave RESTHeart a fixed 60 s, by a `date +%s` deadline, and then slept another 5 s. Bootstrap gave it a caller's count of seconds (240). On a loaded CI host a JVM can take longer than any such count to start: keploy/enterprise's restheart lane recorded nothing when RESTHeart answered after the 60 s, on #2922 (pipeline 11773), with keploy's eBPF load held up by the host. restheart_wait_for_app now waits for RESTHeart to answer its root (200, or 401 asking for credentials) for as long as its container is running. A container that has exited, or is not there, ends the wait at once and fails it, with the container's last log lines. One that runs and never answers is left to the caller's own bound (a CI step's timeout). The 5 s sleep is gone: the app is up once it answers, since its compose starts it only after Mongo is healthy. bootstrap still accepts a seconds argument from callers that pass one (keploy/enterprise's lane passes 240) and ignores it. run-and-measure.sh's 120 x 2 s poll goes too: it now relies on flow.sh bootstrap's wait. The README examples drop the 240. Signed-off-by: slayerjain --- .github/workflows/scripts/run-and-measure.sh | 17 ++----- restheart-mongo/README.md | 6 +-- restheart-mongo/flow.sh | 53 +++++++++++++------- 3 files changed, 43 insertions(+), 33 deletions(-) diff --git a/.github/workflows/scripts/run-and-measure.sh b/.github/workflows/scripts/run-and-measure.sh index 741ddcf3..076aeb19 100755 --- a/.github/workflows/scripts/run-and-measure.sh +++ b/.github/workflows/scripts/run-and-measure.sh @@ -37,16 +37,11 @@ COMPOSE=(docker compose -f docker-compose.yml -f docker-compose.coverage.yml) "${COMPOSE[@]}" up -d --build -# Both 200 and 401 are success signals. -for i in $(seq 1 120); do - code=$(curl -sS -o /dev/null -w '%{http_code}' \ - "http://127.0.0.1:${RESTHEART_APP_PORT}/" 2>/dev/null || echo "") - if [ "$code" = "200" ] || [ "$code" = "401" ]; then break; fi - sleep 2 -done - -if [ "$code" != "200" ] && [ "$code" != "401" ]; then - echo "::error::restheart did not bind on port ${RESTHEART_APP_PORT} within 240s (last code: ${code:-empty})" +# flow.sh bootstrap waits for RESTHeart to answer for as long as its +# container runs (no count of tries: a slow runner is waited for), and +# fails at once when the container has exited. +if ! bash flow.sh bootstrap; then + echo "::error::restheart did not come up on port ${RESTHEART_APP_PORT} (above)" echo "----- restheart container logs -----" docker logs "${RESTHEART_APP_CONTAINER}" --tail 200 2>&1 || true echo "----- mongo container logs -----" @@ -54,8 +49,6 @@ if [ "$code" != "200" ] && [ "$code" != "401" ]; then "${COMPOSE[@]}" down -v --remove-orphans || true exit 1 fi - -bash flow.sh bootstrap 240 bash flow.sh record-traffic # JaCoCo TCP-dump + report (no JVM stop needed). diff --git a/restheart-mongo/README.md b/restheart-mongo/README.md index ba929c4e..58a3c34b 100644 --- a/restheart-mongo/README.md +++ b/restheart-mongo/README.md @@ -34,7 +34,7 @@ restheart-mongo/ The sample is keploy-independent: `docker compose up && bash flow.sh bootstrap && bash flow.sh record-traffic` runs end-to-end against bare RESTHeart. Lane scripts wrap that exact same path inside `keploy record` / `keploy test`. -* `bootstrap` — wait for RESTHeart to start serving and PUT the seed collections (`items`, `people`, `places`, `halpeople`, `relpeople`, `gql-apps`, `acl`, `_schemas`, `avatars.files`, `range_files.files`, `imported_csv`) so subsequent record-traffic calls have something to find. +* `bootstrap` — wait for RESTHeart to answer (for as long as its container runs: no deadline) and PUT the seed collections (`items`, `people`, `places`, `halpeople`, `relpeople`, `gql-apps`, `acl`, `_schemas`, `avatars.files`, `range_files.files`, `imported_csv`) so subsequent record-traffic calls have something to find. * `record-traffic` — drive the full RESTHeart REST surface listed above. Every call is fault-tolerant (`|| true`) so a single transient 4xx never aborts the run. keploy is the assertion layer. * `coverage` — emits real Java line coverage via JaCoCo when the `docker-compose.coverage.yml` overlay is applied; otherwise a no-op (the base image is uninstrumented so this prints an info message and exits 0). @@ -44,7 +44,7 @@ The sample is keploy-independent: `docker compose up && bash flow.sh bootstrap & ```sh docker compose up -d -bash flow.sh bootstrap 240 +bash flow.sh bootstrap bash flow.sh record-traffic docker compose down -v ``` @@ -58,7 +58,7 @@ The base image is uninstrumented. Apply the coverage overlay to attach the JaCoC ```sh mkdir -p coverage docker compose -f docker-compose.yml -f docker-compose.coverage.yml up -d --build -bash flow.sh bootstrap 240 +bash flow.sh bootstrap bash flow.sh record-traffic bash flow.sh coverage docker compose -f docker-compose.yml -f docker-compose.coverage.yml down -v diff --git a/restheart-mongo/flow.sh b/restheart-mongo/flow.sh index 63555487..13443a0b 100644 --- a/restheart-mongo/flow.sh +++ b/restheart-mongo/flow.sh @@ -44,26 +44,42 @@ log_fired() { printf '%s %s\n' "$1" "$2" >>"$RESTHEART_FIRED_ROUTES_FILE" } +# restheart_answers: RESTHeart answers HTTP on its root: 200, or 401 (it +# is up and asks for credentials). A published port is not that: docker's +# proxy listens on it as soon as the container starts. +restheart_answers() { + local code + code=$(curl -sS -m 5 -o /dev/null -w '%{http_code}' "${base}/" 2>/dev/null || true) + [ "$code" = "200" ] || [ "$code" = "401" ] +} + +# restheart_wait_for_app: waits until RESTHeart answers, for as long as its +# container ($RESTHEART_APP_CONTAINER) runs. No deadline: how long a JVM +# takes to start depends on the host, and a deadline that a loaded runner +# outlasts fails a healthy app (60 s here did, under keploy record on a busy +# CI host). A container that has exited, or is not there, ends the wait at +# once and fails it, with its last log lines; one that runs and never +# answers is left to the caller's own bound (a CI step's timeout). A lane +# that waits for the app itself first finds it answering at the first probe. restheart_wait_for_app() { - local timeout=${1:-180} - local start_ts code - start_ts=$(date +%s) - while true; do - code=$(curl -sS -o /dev/null -w '%{http_code}' "${base}/" 2>/dev/null || echo "") - # 401 (auth required on root) is a SUCCESS signal — it - # means RESTHeart is up and responding to HTTP. - if [ "$code" = "200" ] || [ "$code" = "401" ]; then return 0; fi - if [ $(( $(date +%s) - start_ts )) -ge "$timeout" ]; then - echo "restheart_wait_for_app: timed out (last code: ${code:-})" >&2 - return 1 - fi - sleep 2 + local state + while ! restheart_answers; do + state=$(docker inspect -f '{{.State.Status}}' "$RESTHEART_APP_CONTAINER" 2>/dev/null || true) + case "$state" in + running) ;; + *) + # It may have answered, and gone, since the probe. + restheart_answers && return 0 + echo "restheart_wait_for_app: RESTHeart does not answer on ${base}/ and its container ${RESTHEART_APP_CONTAINER} is ${state:-not there}" >&2 + docker logs --tail 50 "$RESTHEART_APP_CONTAINER" >&2 2>&1 || true + return 1 ;; + esac + sleep 1 done } restheart_bootstrap() { - local timeout=${1:-180} - restheart_wait_for_app "$timeout" + restheart_wait_for_app # Seed the collections record-traffic depends on. Each PUT is # idempotent (201 first time, 200 on subsequent runs) and @@ -78,8 +94,7 @@ restheart_bootstrap() { } restheart_record_traffic() { - restheart_wait_for_app 60 - sleep 5 + restheart_wait_for_app local encoded_doc_keys='%7B%22_id%22:1,%22name%22:1,%22age%22:1%7D' local encoded_filter='%7B%22_id%22:%22jane%22%7D' @@ -1315,7 +1330,9 @@ print(miss, cov) } case "${1:-}" in - bootstrap) restheart_bootstrap "${2:-180}" ;; + # A second argument (the seconds a caller once gave the wait) is + # accepted and not used: the wait is on the app, not on a clock. + bootstrap) restheart_bootstrap ;; record-traffic) restheart_record_traffic ;; coverage) restheart_report_coverage ;; *) From 4a65d8e3b357db37682140bd86c99b4b46df92c0 Mon Sep 17 00:00:00 2001 From: slayerjain Date: Tue, 20 Oct 2026 00:05:24 +0530 Subject: [PATCH 2/3] fix(restheart-mongo): flow.sh's wait reads a container's state, not a docker hiccup, as the verdict Review of 6870873: - A container that is created or restarting is on its way to running, so the wait keeps going for it. Only one that has exited, died, or is not there ends the wait. - A `docker inspect` that fails for another reason (a daemon slow under load) is not read as the container being gone. The wait goes on. - run-and-measure.sh's error now says that bootstrap failed, which can be RESTHeart not coming up or the seeding failing, not only the first. Signed-off-by: slayerjain --- .github/workflows/scripts/run-and-measure.sh | 2 +- restheart-mongo/flow.sh | 29 ++++++++++++-------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.github/workflows/scripts/run-and-measure.sh b/.github/workflows/scripts/run-and-measure.sh index 076aeb19..97d407d9 100755 --- a/.github/workflows/scripts/run-and-measure.sh +++ b/.github/workflows/scripts/run-and-measure.sh @@ -41,7 +41,7 @@ COMPOSE=(docker compose -f docker-compose.yml -f docker-compose.coverage.yml) # container runs (no count of tries: a slow runner is waited for), and # fails at once when the container has exited. if ! bash flow.sh bootstrap; then - echo "::error::restheart did not come up on port ${RESTHEART_APP_PORT} (above)" + echo "::error::flow.sh bootstrap failed (above): RESTHeart did not come up on port ${RESTHEART_APP_PORT}, or seeding it failed" echo "----- restheart container logs -----" docker logs "${RESTHEART_APP_CONTAINER}" --tail 200 2>&1 || true echo "----- mongo container logs -----" diff --git a/restheart-mongo/flow.sh b/restheart-mongo/flow.sh index 13443a0b..0fb6b9e2 100644 --- a/restheart-mongo/flow.sh +++ b/restheart-mongo/flow.sh @@ -54,25 +54,32 @@ restheart_answers() { } # restheart_wait_for_app: waits until RESTHeart answers, for as long as its -# container ($RESTHEART_APP_CONTAINER) runs. No deadline: how long a JVM -# takes to start depends on the host, and a deadline that a loaded runner -# outlasts fails a healthy app (60 s here did, under keploy record on a busy -# CI host). A container that has exited, or is not there, ends the wait at -# once and fails it, with its last log lines; one that runs and never +# container ($RESTHEART_APP_CONTAINER) is created, running or restarting. No +# deadline: how long a JVM takes to start depends on the host, and a deadline +# that a loaded runner outlasts fails a healthy app (60 s here did, under +# keploy record on a busy CI host). A container that has exited, or is not +# there, ends the wait at once and fails it, with its last log lines; a docker +# that does not answer is no verdict either way; one that runs and never # answers is left to the caller's own bound (a CI step's timeout). A lane # that waits for the app itself first finds it answering at the first probe. restheart_wait_for_app() { - local state + local state rc while ! restheart_answers; do - state=$(docker inspect -f '{{.State.Status}}' "$RESTHEART_APP_CONTAINER" 2>/dev/null || true) - case "$state" in - running) ;; - *) + rc=0 + state=$(docker inspect -f '{{.State.Status}}' "$RESTHEART_APP_CONTAINER" 2>&1) || rc=$? + case "$rc:$state" in + # Not answering yet, but on its way: created, starting, restarting. + 0:created | 0:running | 0:restarting) ;; + # Gone: it exited, died, or is not there. + 0:* | *"No such"*) # It may have answered, and gone, since the probe. restheart_answers && return 0 - echo "restheart_wait_for_app: RESTHeart does not answer on ${base}/ and its container ${RESTHEART_APP_CONTAINER} is ${state:-not there}" >&2 + [ "$rc" = 0 ] || state="not there" + echo "restheart_wait_for_app: RESTHeart does not answer on ${base}/ and its container ${RESTHEART_APP_CONTAINER} is ${state}" >&2 docker logs --tail 50 "$RESTHEART_APP_CONTAINER" >&2 2>&1 || true return 1 ;; + # docker did not answer (a daemon slow under load): not a verdict. + *) ;; esac sleep 1 done From 4e21388a182dc34dca62c05b90618c6a69574ef8 Mon Sep 17 00:00:00 2001 From: slayerjain Date: Tue, 20 Oct 2026 03:37:26 +0530 Subject: [PATCH 3/3] fix(restheart-mongo): flow.sh fails when docker cannot run, and says once when it does not answer Review of 4a65d8e: every docker error other than "No such" was taken as transient, silently and for ever, including exit 126/127 (no docker to run) and a socket it may not open. 126/127 now fail the wait at once, with what docker said; any other error is said once, and the wait goes on. Signed-off-by: slayerjain --- restheart-mongo/flow.sh | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/restheart-mongo/flow.sh b/restheart-mongo/flow.sh index 0fb6b9e2..8687ca7c 100644 --- a/restheart-mongo/flow.sh +++ b/restheart-mongo/flow.sh @@ -63,11 +63,15 @@ restheart_answers() { # answers is left to the caller's own bound (a CI step's timeout). A lane # that waits for the app itself first finds it answering at the first probe. restheart_wait_for_app() { - local state rc + local state rc said="" while ! restheart_answers; do rc=0 state=$(docker inspect -f '{{.State.Status}}' "$RESTHEART_APP_CONTAINER" 2>&1) || rc=$? case "$rc:$state" in + # No docker to ask (not installed, not executable): no wait can end. + 126:* | 127:*) + echo "restheart_wait_for_app: cannot run docker (exit $rc): $state" >&2 + return 1 ;; # Not answering yet, but on its way: created, starting, restarting. 0:created | 0:running | 0:restarting) ;; # Gone: it exited, died, or is not there. @@ -78,8 +82,11 @@ restheart_wait_for_app() { echo "restheart_wait_for_app: RESTHeart does not answer on ${base}/ and its container ${RESTHEART_APP_CONTAINER} is ${state}" >&2 docker logs --tail 50 "$RESTHEART_APP_CONTAINER" >&2 2>&1 || true return 1 ;; - # docker did not answer (a daemon slow under load): not a verdict. - *) ;; + # docker did not answer (a daemon slow under load, a socket it + # may not open): not a verdict, said once. + *) + [ -n "$said" ] || echo "restheart_wait_for_app: docker inspect failed (exit $rc), still waiting: $state" >&2 + said=1 ;; esac sleep 1 done