Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
74f6b83
[#888] Name the trees of a JDBC backend from a catalog in the database
vharseko Aug 20, 2026
e49fce2
[#888] Take a tree out of the catalog in the commit that drops its table
vharseko Aug 21, 2026
87d3bb5
[#888] Tell what a clear left standing apart by its tree stamp, and u…
vharseko Aug 21, 2026
f51a1c1
[#888] Ask for a table in the database the transaction works in, and …
vharseko Aug 26, 2026
7498571
[#888] Record a tree in the catalog when it is not there, and commit …
vharseko Sep 1, 2026
1d1b1d6
Merge remote-tracking branch 'origin/master' into issues/888-jdbc-tre…
vharseko Sep 1, 2026
f96be02
Merge remote-tracking branch 'origin/master' into issues/888-jdbc-tre…
vharseko Sep 1, 2026
06fdb7e
[#888] Write the tree catalog on a connection of its own, and ask whe…
vharseko Sep 2, 2026
9a128aa
Merge remote-tracking branch 'origin/master' into issues/888-jdbc-tre…
vharseko Sep 2, 2026
9038594
[#888] Point the scope at the transaction the merge moved its lookup to
vharseko Sep 2, 2026
58c6876
[#888] Establish the catalog connection the way a pooled one is estab…
vharseko Sep 2, 2026
d759363
[#888] Give PgSqlTestCase the sequential=true its own case needs
vharseko Sep 2, 2026
f305785
Merge remote-tracking branch 'origin/master' into issues/888-jdbc-tre…
vharseko Sep 3, 2026
66579d3
[#888] Bound the catalog connect the way a pooled one is bounded, and…
vharseko Sep 3, 2026
dd19865
[#888] Retry the catalog connect the way the pool does, and make a cl…
vharseko Sep 5, 2026
db7a924
Merge remote-tracking branch 'origin/master' into issues/888-jdbc-tre…
vharseko Sep 5, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.LongFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -234,6 +235,54 @@ public class CachedConnection implements Connection {
/** Where the sweep closes what it reaped, so that a close which does not return keeps it: see {@link Pool#sweep}. */
private static volatile Executor closer = DIRECT_EXECUTOR;

/**
* Returns the bound of one attempt to establish a connection, as configured by the {@value
* #CONNECT_TIMEOUT_PROPERTY} system property; 0 for the operator asking for no bound of its own.
* A value beyond what a millisecond bound can carry is taken down to it: three of the four
* dialects state their properties in milliseconds, and a value that saturates the conversion
* bounds nothing.
* <p>
* Read here rather than at each connect so that every connection this backend establishes is
* bounded by the same configured value - the borrows of this pool and the connection {@code
* JDBCStorage} opens outside it for the tree catalog of a backend (#888) alike. A connect
* bounded tighter than the login of the deployment takes is a backend that stops opening, and
* one place to read the property is what keeps the two from drifting apart.
*/
static long getConnectTimeoutSeconds() {
return Math.min(getNonNegativeProperty(CONNECT_TIMEOUT_PROPERTY, DEFAULT_CONNECT_TIMEOUT_SECONDS, "s"),
Integer.MAX_VALUE / 1000);
}

/**
* Returns the deadline of a whole borrow, as configured by the {@value #POOL_TIMEOUT_PROPERTY}
* system property; 0 for the operator asking for no deadline at all.
* <p>
* Read here for the reason the bound of a connect is: it is what a database taking no
* connection for the moment is waited out for, and the connection {@code JDBCStorage} opens
* outside this pool for the tree catalog of a backend (#888) waits it out for exactly as long -
* one property, one meaning, whichever of the two is asking.
*/
static long getPoolTimeoutSeconds() {
return getNonNegativeProperty(POOL_TIMEOUT_PROPERTY, DEFAULT_POOL_TIMEOUT_SECONDS, "s");
}

/**
* The moment a borrow of this length gives up, or {@link Long#MAX_VALUE} where it gives up
* never - a property of 0, and a value so large that the milliseconds of it would overflow.
* <p>
* The sum is guarded and not only the product: a value under the clamp above but large enough
* that the moment it names is past the end of the epoch would wrap to a deadline already behind
* us, and a borrow configured to wait practically forever would give up on its first retryable
* failure - the opposite of what was asked for.
*/
static long deadlineOf(long startedAt, long poolTimeoutSeconds) {
if (poolTimeoutSeconds == 0 || poolTimeoutSeconds >= Long.MAX_VALUE / 1000) {
return Long.MAX_VALUE;
}
final long deadline = startedAt + poolTimeoutSeconds * 1000;
return deadline < startedAt ? Long.MAX_VALUE : deadline;
}

/**
* Returns the time after which an idle pooled connection is closed, as configured by the
* {@value #TTL_PROPERTY} system property. An invalid value is ignored in favor of the default.
Expand Down Expand Up @@ -1079,14 +1128,11 @@ static Connection getConnection(String connectionString, boolean trusted) throws
final Pool pool = poolOf(connectionString);
final ConnectDialect dialect = ConnectDialect.of(connectionString);
reportUnknownDialect(connectionString, dialect);
final long connectTimeoutSeconds = Math.min(
getNonNegativeProperty(CONNECT_TIMEOUT_PROPERTY, DEFAULT_CONNECT_TIMEOUT_SECONDS, "s"),
Integer.MAX_VALUE / 1000);
final long poolTimeoutSeconds = getNonNegativeProperty(POOL_TIMEOUT_PROPERTY, DEFAULT_POOL_TIMEOUT_SECONDS, "s");
final long connectTimeoutSeconds = getConnectTimeoutSeconds();
final long poolTimeoutSeconds = getPoolTimeoutSeconds();
final long ttlMillis = getCacheTtlMillis();
final long startedAt = System.currentTimeMillis();
final long deadline = (poolTimeoutSeconds == 0 || poolTimeoutSeconds >= Long.MAX_VALUE / 1000)
? Long.MAX_VALUE : startedAt + poolTimeoutSeconds * 1000;
final long deadline = deadlineOf(startedAt, poolTimeoutSeconds);
// A thread already holding a connection is not made to wait for one: the two are held at
// the same time, so waiting for the first to come back would wait for itself.
final boolean reentrant = pool.heldByCurrentThread();
Expand Down Expand Up @@ -1493,18 +1539,53 @@ private static void warnPoolFull(String connectionString, String message) {
// is indistinguishable from a hang. Throttled, since every operation of the backend borrows
// through here and would otherwise log a copy of its own.
private static void warnStall(String connectionString, int attempts, long startedAt, SQLException cause) {
warnStall(connectionString, "", startedAt,
waitedMs -> stallMessage(connectionString, attempts, waitedMs, cause));
}

/**
* The same for a connect this class makes for somebody outside the pool - the connection the
* tree catalog of a backend is written on (#888) - which waits for no pooled connection and
* must not be described as one.
* <p>
* Throttled apart from the borrows of the same url as well as worded apart from them: the two
* stall on the same database for the same reason, so a borrow that warned a moment ago would
* otherwise silence the connect that is about to fail - the one of the two an operator has no
* other line about.
*/
static void warnStallOutsidePool(String connectionString, String what, int attempts, long startedAt,
SQLException cause) {
warnStall(connectionString, "|" + what, startedAt,
waitedMs -> outsidePoolStallMessage(connectionString, what, attempts, waitedMs, cause));
}

private static void warnStall(String connectionString, String throttleKeySuffix, long startedAt,
LongFunction<String> message) {
final long now = System.currentTimeMillis();
if (now - startedAt < STALL_WARNING_AFTER_MS) {
return;
}
final AtomicLong lastOfThisUrl =
lastStallWarning.computeIfAbsent(safeUrl(connectionString), url -> new AtomicLong());
lastStallWarning.computeIfAbsent(safeUrl(connectionString) + throttleKeySuffix, url -> new AtomicLong());
final long last = lastOfThisUrl.get();
if (now - last >= STALL_WARNING_INTERVAL_MS && lastOfThisUrl.compareAndSet(last, now)) {
logger.warn(LocalizableMessage.raw("%s", stallMessage(connectionString, attempts, now - startedAt, cause)));
logger.warn(LocalizableMessage.raw("%s", message.apply(now - startedAt)));
}
}

/**
* The stall of a connect made outside the pool, as it reaches the log. Built apart from the
* logging of it for the reason {@link #stallMessage} is: the rule it has to keep - neither the
* connection string nor the message of the driver reaches a log as it stands - is a rule a test
* can hold it to.
*/
static String outsidePoolStallMessage(String connectionString, String what, int attempts, long waitedMs,
SQLException cause) {
return String.format("%s takes no further connection: the %s connection of this backend is opened outside the"
+ " pool and has been retrying for %d ms (%d attempts), last error: %s", safeUrl(connectionString), what,
waitedMs, attempts, redact(cause.getMessage(), connectionString));
}

/**
* The stall as it reaches the log. Built apart from the logging of it so that the rule it has
* to keep - neither the connection string nor the message of the driver reaches a log as it
Expand Down
Loading
Loading