Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions ps-cache-kotlin/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -q
COPY src/ src/
RUN mvn package -DskipTests -q

FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/kotlin-app-1.0.0.jar app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]
34 changes: 34 additions & 0 deletions ps-cache-kotlin/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: testdb
ports:
- "5433:5432"

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DB is published on host port 5433, but the app defaults to DB_PORT=5432. If you expect running the app locally against the compose Postgres (without the api service), consider either mapping 5432:5432 or documenting that local runs need DB_PORT=5433 to avoid connection confusion.

Suggested change
- "5433:5432"
- "5432:5432"

Copilot uses AI. Check for mistakes.

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

db is published on host port 5433, but the app’s default DB_PORT in application.properties is 5432. If someone runs Postgres via this compose file and runs the app locally (not in the api container), the default connection settings will point to the wrong port. Consider either mapping 5432:5432 here, or changing the default DB_PORT to 5433 (and documenting the intended run mode).

Suggested change
- "5433:5432"
- "5432:5432"

Copilot uses AI. Check for mistakes.
volumes:
- pgdata:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 2s
timeout: 5s
retries: 5

api:
build: .
ports:
- "8080:8080"
environment:
DB_HOST: db
DB_PORT: "5432"
DB_USER: postgres
DB_PASSWORD: postgres
DB_NAME: testdb
depends_on:
db:
condition: service_healthy

volumes:
pgdata:
15 changes: 15 additions & 0 deletions ps-cache-kotlin/init.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
CREATE SCHEMA IF NOT EXISTS travelcard;

CREATE TABLE IF NOT EXISTS travelcard.travel_account (
id SERIAL PRIMARY KEY,
member_id INT NOT NULL UNIQUE,
name TEXT NOT NULL,
balance INT NOT NULL DEFAULT 0
);

INSERT INTO travelcard.travel_account (member_id, name, balance) VALUES
(19, 'Alice', 1000),
(23, 'Bob', 2500),
(31, 'Charlie', 500),
(42, 'Diana', 7500)
ON CONFLICT (member_id) DO NOTHING;
44 changes: 44 additions & 0 deletions ps-cache-kotlin/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
</parent>
<groupId>com.demo</groupId>
<artifactId>kotlin-app</artifactId>
<version>1.0.0</version>
<properties>
<java.version>21</java.version>
<kotlin.version>1.9.25</kotlin.version>
</properties>
<dependencies>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency>
<dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId></dependency>
<dependency><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-reflect</artifactId></dependency>
<dependency><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-stdlib</artifactId></dependency>
</dependencies>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<configuration>
<compilerPlugins><plugin>spring</plugin></compilerPlugins>
<jvmTarget>${java.version}</jvmTarget>
</configuration>
<dependencies>
<dependency><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-maven-allopen</artifactId><version>${kotlin.version}</version></dependency>
</dependencies>
<executions><execution><id>compile</id><goals><goal>compile</goal></goals></execution></executions>
</plugin>
<plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin>
</plugins>
</build>
</project>
91 changes: 91 additions & 0 deletions ps-cache-kotlin/src/main/kotlin/com/demo/App.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.demo

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import javax.sql.DataSource
import com.zaxxer.hikari.HikariDataSource

@SpringBootApplication
class App

fun main(args: Array<String>) {
runApplication<App>(*args)
}

data class Account(
val id: Int,
val memberId: Int,
val name: String,
val balance: Int
)

@RestController
class AccountController(private val jdbc: JdbcTemplate, private val dataSource: DataSource) {

@GetMapping("/health")
fun health() = mapOf("status" to "ok")

/**
* /account?member=N queries the travel_account table.
*
* JDBC PS caching (prepareThreshold=1):
* - 1st call on a fresh connection: Parse(query="SELECT ...") + Bind + Describe + Execute
* - 2nd+ calls on same connection: Bind(ps="S_1") + Execute only (cached PS)
*
* The /evict endpoint forces HikariCP to evict all connections, so the
* NEXT /account call gets a fresh connection with cold PS cache.

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This KDoc says /evict "evict[s] all connections", but the later comment for /evict says it evicts only idle connections. Align the documentation with the actual behavior of softEvictConnections() to avoid confusion when interpreting test results.

Suggested change
* The /evict endpoint forces HikariCP to evict all connections, so the
* NEXT /account call gets a fresh connection with cold PS cache.
* The /evict endpoint forces HikariCP to soft-evict all idle connections,
* so the NEXT /account call typically gets a fresh connection with a cold PS cache.

Copilot uses AI. Check for mistakes.
*/
@GetMapping("/account")
fun getAccount(@RequestParam("member") memberId: Int): Any {
return jdbc.execute { conn: java.sql.Connection ->
conn.autoCommit = false
try {
val ps = conn.prepareStatement(
"""SELECT id, member_id, name, balance
FROM travelcard.travel_account
WHERE member_id = ?"""
)
ps.setInt(1, memberId)
val rs = ps.executeQuery()

val result = if (rs.next()) {
Account(
id = rs.getInt("id"),
memberId = rs.getInt("member_id"),
name = rs.getString("name"),
balance = rs.getInt("balance")
)
} else {
mapOf("error" to "not found", "member_id" to memberId)
}

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/account returns an error map with HTTP 200 when the member isn’t found. If this is meant to behave like a real API (and to make failures obvious in tests), return a 404 status (e.g., via ResponseEntity.notFound()), or otherwise clearly signal the failure in the HTTP response code.

Copilot uses AI. Check for mistakes.

rs.close()
ps.close()

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If anything throws after prepareStatement/executeQuery, the ResultSet/PreparedStatement won’t be closed (they’re only closed on the happy path). Use Kotlin use {} (or a finally) around ps and rs so they are always closed before the connection is returned to the pool.

Suggested change
val ps = conn.prepareStatement(
"""SELECT id, member_id, name, balance
FROM travelcard.travel_account
WHERE member_id = ?"""
)
ps.setInt(1, memberId)
val rs = ps.executeQuery()
val result = if (rs.next()) {
Account(
id = rs.getInt("id"),
memberId = rs.getInt("member_id"),
name = rs.getString("name"),
balance = rs.getInt("balance")
)
} else {
mapOf("error" to "not found", "member_id" to memberId)
}
rs.close()
ps.close()
val result = conn.prepareStatement(
"""SELECT id, member_id, name, balance
FROM travelcard.travel_account
WHERE member_id = ?"""
).use { ps ->
ps.setInt(1, memberId)
ps.executeQuery().use { rs ->
if (rs.next()) {
Account(
id = rs.getInt("id"),
memberId = rs.getInt("member_id"),
name = rs.getString("name"),
balance = rs.getInt("balance")
)
} else {
mapOf("error" to "not found", "member_id" to memberId)
}
}
}

Copilot uses AI. Check for mistakes.
conn.commit()
result
} catch (e: Exception) {
conn.rollback()
throw e
}
}!!

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The non-null assertion on the result of jdbc.execute { ... } can throw an NPE at runtime (JdbcTemplate’s execute returns a nullable type). Prefer returning a non-null value without !! (e.g., use the generic execute<T>/ConnectionCallback<T> form or handle the null case explicitly).

Suggested change
}!!
} ?: throw IllegalStateException(
"Database query returned no result; retry the request or verify the database connection state."
)

Copilot uses AI. Check for mistakes.
}

/**
* /evict forces HikariCP to evict all idle connections.
* Next request gets a FRESH PG connection → cold PS cache.
* This simulates what happens in production when connections cycle.
*/
@GetMapping("/evict")
fun evict(): Map<String, Any> {
val hikari = dataSource as HikariDataSource
hikari.hikariPoolMXBean?.softEvictConnections()
// Also wait briefly for eviction

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/evict is an unauthenticated endpoint that churns the connection pool. If this sample is ever exposed beyond local/dev, it’s an easy DoS vector; consider guarding it behind a profile/property, requiring a shared-secret header, or clearly documenting it as dev-only.

Copilot uses AI. Check for mistakes.
Thread.sleep(200)

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/evict blocks the request thread with a fixed Thread.sleep(200), which adds latency and makes eviction timing dependent on a hard-coded delay. Prefer returning immediately (the caller already waits), or poll for the pool state with a bounded timeout if you need to ensure a new connection is established before responding.

Suggested change
Thread.sleep(200)

Copilot uses AI. Check for mistakes.
return mapOf("evicted" to true, "active" to (hikari.hikariPoolMXBean?.activeConnections ?: 0),
"idle" to (hikari.hikariPoolMXBean?.idleConnections ?: 0))

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The endpoint reports evicted=true even if hikari.hikariPoolMXBean is null, which can yield a false-positive success response. Consider returning an error/evicted=false when the MXBean isn’t available, or ensure it’s non-null before responding.

Suggested change
hikari.hikariPoolMXBean?.softEvictConnections()
// Also wait briefly for eviction
Thread.sleep(200)
return mapOf("evicted" to true, "active" to (hikari.hikariPoolMXBean?.activeConnections ?: 0),
"idle" to (hikari.hikariPoolMXBean?.idleConnections ?: 0))
val poolMxBean = hikari.hikariPoolMXBean
?: return mapOf(
"evicted" to false,
"error" to "Hikari pool MXBean is unavailable; verify Hikari management is enabled and retry the request."
)
poolMxBean.softEvictConnections()
// Also wait briefly for eviction
Thread.sleep(200)
return mapOf(
"evicted" to true,
"active" to poolMxBean.activeConnections,
"idle" to poolMxBean.idleConnections
)

Copilot uses AI. Check for mistakes.
}
}
7 changes: 7 additions & 0 deletions ps-cache-kotlin/src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
server.port=8080
spring.datasource.url=jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:testdb}?prepareThreshold=1&preparedStatementCacheQueries=256

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DB port defaults to 5432 in application.properties, but docker-compose.yml exposes Postgres on host port 5433. If the intended workflow includes running the app locally against the compose DB, consider aligning the default (DB_PORT) or the compose port mapping to avoid connection failures without extra env vars.

Suggested change
spring.datasource.url=jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:testdb}?prepareThreshold=1&preparedStatementCacheQueries=256
spring.datasource.url=jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5433}/${DB_NAME:testdb}?prepareThreshold=1&preparedStatementCacheQueries=256

Copilot uses AI. Check for mistakes.
spring.datasource.username=${DB_USER:postgres}
spring.datasource.password=${DB_PASSWORD:postgres}
spring.datasource.hikari.maximum-pool-size=1
spring.datasource.hikari.minimum-idle=1
spring.sql.init.mode=never
38 changes: 38 additions & 0 deletions ps-cache-kotlin/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail

BASE_URL="http://localhost:8080"

echo "=== PS-Cache Mock Mismatch Test (Kotlin/JDBC) ==="

echo "--- Window 1: Connection A ---"
echo " /account?member=19:"
curl -s "$BASE_URL/account?member=19"
echo ""
sleep 0.3

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sleep 0.3 relies on non-POSIX fractional sleep support and can fail on some environments/shells. Consider using integer sleeps or an alternative portable delay mechanism so this script runs consistently across platforms.

Copilot uses AI. Check for mistakes.
echo " /account?member=23:"
curl -s "$BASE_URL/account?member=23"
echo ""
sleep 0.3

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sleep 0.3 relies on non-POSIX fractional sleep support and can fail on some environments/shells. Consider using integer sleeps or an alternative portable delay mechanism so this script runs consistently across platforms.

Copilot uses AI. Check for mistakes.
echo ""
echo "--- Evict (force new connection) ---"
echo " /evict:"
curl -s "$BASE_URL/evict"
echo ""
sleep 1

echo ""
echo "--- Window 2: Connection B ---"
echo " /account?member=31:"
curl -s "$BASE_URL/account?member=31"
echo ""
sleep 0.3

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sleep 0.3 relies on non-POSIX fractional sleep support and can fail on some environments/shells. Consider using integer sleeps or an alternative portable delay mechanism so this script runs consistently across platforms.

Copilot uses AI. Check for mistakes.
echo " /account?member=42:"
curl -s "$BASE_URL/account?member=42"

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This uses curl -s, which will not fail the script on HTTP 4xx/5xx responses. For a regression test, use curl’s fail-on-error behavior (e.g., -f plus -S) so server errors cause a non-zero exit.

Suggested change
curl -s "$BASE_URL/account?member=19"
echo ""
sleep 0.3
echo " /account?member=23:"
curl -s "$BASE_URL/account?member=23"
echo ""
sleep 0.3
echo ""
echo "--- Evict (force new connection) ---"
echo " /evict:"
curl -s "$BASE_URL/evict"
echo ""
sleep 1
echo ""
echo "--- Window 2: Connection B ---"
echo " /account?member=31:"
curl -s "$BASE_URL/account?member=31"
echo ""
sleep 0.3
echo " /account?member=42:"
curl -s "$BASE_URL/account?member=42"
curl -fsS "$BASE_URL/account?member=19"
echo ""
sleep 0.3
echo " /account?member=23:"
curl -fsS "$BASE_URL/account?member=23"
echo ""
sleep 0.3
echo ""
echo "--- Evict (force new connection) ---"
echo " /evict:"
curl -fsS "$BASE_URL/evict"
echo ""
sleep 1
echo ""
echo "--- Window 2: Connection B ---"
echo " /account?member=31:"
curl -fsS "$BASE_URL/account?member=31"
echo ""
sleep 0.3
echo " /account?member=42:"
curl -fsS "$BASE_URL/account?member=42"

Copilot uses AI. Check for mistakes.

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This uses curl -s, which will not fail the script on HTTP 4xx/5xx responses. For a regression test, use curl’s fail-on-error behavior (e.g., -f plus -S) so server errors cause a non-zero exit.

Suggested change
curl -s "$BASE_URL/account?member=19"
echo ""
sleep 0.3
echo " /account?member=23:"
curl -s "$BASE_URL/account?member=23"
echo ""
sleep 0.3
echo ""
echo "--- Evict (force new connection) ---"
echo " /evict:"
curl -s "$BASE_URL/evict"
echo ""
sleep 1
echo ""
echo "--- Window 2: Connection B ---"
echo " /account?member=31:"
curl -s "$BASE_URL/account?member=31"
echo ""
sleep 0.3
echo " /account?member=42:"
curl -s "$BASE_URL/account?member=42"
curl -fsS "$BASE_URL/account?member=19"
echo ""
sleep 0.3
echo " /account?member=23:"
curl -fsS "$BASE_URL/account?member=23"
echo ""
sleep 0.3
echo ""
echo "--- Evict (force new connection) ---"
echo " /evict:"
curl -fsS "$BASE_URL/evict"
echo ""
sleep 1
echo ""
echo "--- Window 2: Connection B ---"
echo " /account?member=31:"
curl -fsS "$BASE_URL/account?member=31"
echo ""
sleep 0.3
echo " /account?member=42:"
curl -fsS "$BASE_URL/account?member=42"

Copilot uses AI. Check for mistakes.

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This uses curl -s, which will not fail the script on HTTP 4xx/5xx responses. For a regression test, use curl’s fail-on-error behavior (e.g., -f plus -S) so server errors cause a non-zero exit.

Suggested change
curl -s "$BASE_URL/account?member=19"
echo ""
sleep 0.3
echo " /account?member=23:"
curl -s "$BASE_URL/account?member=23"
echo ""
sleep 0.3
echo ""
echo "--- Evict (force new connection) ---"
echo " /evict:"
curl -s "$BASE_URL/evict"
echo ""
sleep 1
echo ""
echo "--- Window 2: Connection B ---"
echo " /account?member=31:"
curl -s "$BASE_URL/account?member=31"
echo ""
sleep 0.3
echo " /account?member=42:"
curl -s "$BASE_URL/account?member=42"
curl -fsS "$BASE_URL/account?member=19"
echo ""
sleep 0.3
echo " /account?member=23:"
curl -fsS "$BASE_URL/account?member=23"
echo ""
sleep 0.3
echo ""
echo "--- Evict (force new connection) ---"
echo " /evict:"
curl -fsS "$BASE_URL/evict"
echo ""
sleep 1
echo ""
echo "--- Window 2: Connection B ---"
echo " /account?member=31:"
curl -fsS "$BASE_URL/account?member=31"
echo ""
sleep 0.3
echo " /account?member=42:"
curl -fsS "$BASE_URL/account?member=42"

Copilot uses AI. Check for mistakes.

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This uses curl -s, which will not fail the script on HTTP 4xx/5xx responses. For a regression test, use curl’s fail-on-error behavior (e.g., -f plus -S) so server errors cause a non-zero exit.

Suggested change
curl -s "$BASE_URL/account?member=19"
echo ""
sleep 0.3
echo " /account?member=23:"
curl -s "$BASE_URL/account?member=23"
echo ""
sleep 0.3
echo ""
echo "--- Evict (force new connection) ---"
echo " /evict:"
curl -s "$BASE_URL/evict"
echo ""
sleep 1
echo ""
echo "--- Window 2: Connection B ---"
echo " /account?member=31:"
curl -s "$BASE_URL/account?member=31"
echo ""
sleep 0.3
echo " /account?member=42:"
curl -s "$BASE_URL/account?member=42"
curl -fsS "$BASE_URL/account?member=19"
echo ""
sleep 0.3
echo " /account?member=23:"
curl -fsS "$BASE_URL/account?member=23"
echo ""
sleep 0.3
echo ""
echo "--- Evict (force new connection) ---"
echo " /evict:"
curl -fsS "$BASE_URL/evict"
echo ""
sleep 1
echo ""
echo "--- Window 2: Connection B ---"
echo " /account?member=31:"
curl -fsS "$BASE_URL/account?member=31"
echo ""
sleep 0.3
echo " /account?member=42:"
curl -fsS "$BASE_URL/account?member=42"

Copilot uses AI. Check for mistakes.

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This uses curl -s, which will not fail the script on HTTP 4xx/5xx responses. For a regression test, use curl’s fail-on-error behavior (e.g., -f plus -S) so server errors cause a non-zero exit.

Suggested change
curl -s "$BASE_URL/account?member=19"
echo ""
sleep 0.3
echo " /account?member=23:"
curl -s "$BASE_URL/account?member=23"
echo ""
sleep 0.3
echo ""
echo "--- Evict (force new connection) ---"
echo " /evict:"
curl -s "$BASE_URL/evict"
echo ""
sleep 1
echo ""
echo "--- Window 2: Connection B ---"
echo " /account?member=31:"
curl -s "$BASE_URL/account?member=31"
echo ""
sleep 0.3
echo " /account?member=42:"
curl -s "$BASE_URL/account?member=42"
curl -fsS "$BASE_URL/account?member=19"
echo ""
sleep 0.3
echo " /account?member=23:"
curl -fsS "$BASE_URL/account?member=23"
echo ""
sleep 0.3
echo ""
echo "--- Evict (force new connection) ---"
echo " /evict:"
curl -fsS "$BASE_URL/evict"
echo ""
sleep 1
echo ""
echo "--- Window 2: Connection B ---"
echo " /account?member=31:"
curl -fsS "$BASE_URL/account?member=31"
echo ""
sleep 0.3
echo " /account?member=42:"
curl -fsS "$BASE_URL/account?member=42"

Copilot uses AI. Check for mistakes.
echo ""

echo ""
echo "=== Done ==="
Loading