mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Valery Kharseko
2 days ago cf2068420f92f25985c22a6cdb16c17d9ceb1efb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2024-2026 3A Systems, LLC.
 */
package org.opends.server.backends.jdbc;
 
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.opends.server.api.WorkQueue;
import org.opends.server.core.DirectoryServer;
 
import java.sql.*;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
public class CachedConnection implements Connection {
    private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
 
    // What has been reported once already. Every one of these reports a setting rather than an
    // event - a property that is not a number, a url no bound of this class can reach, a driver
    // whose property names are not known here - so it does not become truer by being repeated,
    // and every operation of the backend comes through here.
    // Declared above every field whose initializer can reach warnOnce(): class variable
    // initializers run in textual order (JLS 12.4.2), so a set declared below aliveBypassNanos
    // would still be null the moment a property this class reports on carries a value worth
    // warning about - a window longer than the ttl, or one that is not a number - and the report
    // would leave the class uninitializable rather than merely configured oddly.
    static final Set<String> warnedOnce = ConcurrentHashMap.newKeySet();
 
    static final String TTL_PROPERTY = "org.openidentityplatform.opendj.jdbc.ttl";
    static final long DEFAULT_TTL_MS = 15000;
 
    /**
     * How long a pooled connection is handed out without being validated after it was last proven
     * alive, in ms; 0 validates every borrow, the way this pool did before the window existed.
     * <p>
     * The validation of a connection is a round trip of its own - an empty query on postgresql, a
     * ping on mysql, a round trip of its own on oracle and sql server - and every operation of
     * this backend pays it next to the single statement the operation came for. It earns that on a
     * connection that has been sitting in the pool, which the database or a firewall may have
     * dropped in the meantime; it earns nothing on one that answered a moment ago, which is most
     * of them under load. So a connection proven alive within this window is trusted rather than
     * validated, the way the aliveBypassWindow of HikariCP does it.
     */
    static final String ALIVE_BYPASS_PROPERTY = "org.openidentityplatform.opendj.jdbc.alive.bypass";
    static final long DEFAULT_ALIVE_BYPASS_MS = 500;
 
    /**
     * The longest window this class uses, whatever {@value #ALIVE_BYPASS_PROPERTY} and the
     * {@value #TTL_PROPERTY} it is clamped to say. The clamp to the ttl alone does not bound it:
     * the ttl has no upper bound of its own, and with both set high enough the conversion to
     * nanoseconds saturates - the window then outlasts every reading it is compared against, and
     * no connection of the pool is ever validated again. An hour is already far past what this
     * window is about, which is a connection that answered a moment ago.
     * <p>
     * A compile-time constant, so that it holds its value wherever it is read from: the initializer
     * of {@link #aliveBypassNanos} reaches it, and a field initialized in declaration order would
     * still be 0 there if it were ever moved below (JLS 12.4.2).
     */
    static final long MAX_ALIVE_BYPASS_MS = 60 * 60 * 1000L;
 
    // Read once, at class initialization: every operation of this backend borrows a connection,
    // and the borrow is not the place to parse a system property. Not final so that a test can
    // vary the window without a class loader of its own, and volatile because a non-final static
    // long is written neither atomically nor visibly to the threads reading it (JLS 17.7) - every
    // worker of the backend and every replay thread reads this one.
    static volatile long aliveBypassNanos = TimeUnit.MILLISECONDS.toNanos(getAliveBypassMillis());
 
    /**
     * Bounds the connect and the login of one attempt to establish a connection, in seconds; 0 for
     * no bound of its own - the deadline of {@value #POOL_TIMEOUT_PROPERTY} still bounds the
     * attempt, since it stands for the whole borrow. Setting both to 0 is what leaves a connect
     * unbounded.
     */
    static final String CONNECT_TIMEOUT_PROPERTY = "org.openidentityplatform.opendj.jdbc.connect.timeout";
    static final long DEFAULT_CONNECT_TIMEOUT_SECONDS = 30;
 
    /**
     * Bounds a whole borrow - every connect attempt and every wait for a pooled connection - in
     * seconds; 0 for no bound. Not to the millisecond: the connection in hand is validated
     * whatever the deadline says, and an attempt is never given less than a second, so a borrow
     * can return a validation and a last attempt past it.
     */
    static final String POOL_TIMEOUT_PROPERTY = "org.openidentityplatform.opendj.jdbc.pool.timeout";
    static final long DEFAULT_POOL_TIMEOUT_SECONDS = 60;
 
    /** Bound of the validation of a pooled connection: isValid(0) means "no timeout" in the JDBC contract. */
    static final int VALIDATION_TIMEOUT_SECONDS = 5;
 
    /** 08001, sqlclient_unable_to_establish_sqlconnection: the state of a connect that did not happen. */
    private static final String CONNECT_FAILED_SQL_STATE = "08001";
    /** 53300, too_many_connections: how the standard - and postgresql - reports a server taking no further connection. */
    private static final String CONNECTION_LIMIT_SQL_STATE = "53300";
    /** 57P03, cannot_connect_now: postgresql starting up, shutting down or in recovery. */
    private static final String NOT_ACCEPTING_YET_SQL_STATE = "57P03";
 
    /**
     * The greatest number of connections one pool holds to one database; 0 for no bound. Read once
     * per pool, when the first borrow of a connection string creates it, unlike the bounds of a
     * borrow above: a pool is never removed from the map, and the permits of one already created
     * are not resized, so this one takes a restart of the server to change.
     */
    static final String POOL_MAX_PROPERTY = "org.openidentityplatform.opendj.jdbc.pool.max";
    /**
     * Sized like the worker thread pool the server sizes for itself
     * ({@code Platform.computeNumberOfThreads(16, 2)}), since an operation borrows one connection for
     * its duration: the bound is there to keep a burst from opening as many connections as the
     * database will accept, not to throttle steady traffic.
     * <p>
     * That formula is only what {@code WorkQueue.computeNumWorkerThreads} falls back to. A configured
     * {@code ds-cfg-num-worker-threads} replaces it outright, and there is no default that can follow
     * it: this bound belongs to a database that two backends may share, while that count belongs to
     * the server. So an installation that raised it is told at open where the two stand, by
     * {@link #reportBoundBelowBorrowers}, rather than left to find the wait in a latency graph.
     */
    static final int DEFAULT_POOL_MAX = Math.max(16, Runtime.getRuntime().availableProcessors() * 2);
 
    /** How long a borrow waits for a connection to be returned before looking at the pool again. */
    private static final long POOL_FULL_POLL_MS = 250;
    /** The sweep runs at half the TTL, and no more often than this. */
    private static final long MIN_SWEEP_INTERVAL_MS = 1000;
 
    static final long MAX_BACKOFF_MS = 1000;
    static final long STALL_WARNING_AFTER_MS = 1000;
    static final long STALL_WARNING_INTERVAL_MS = 10000;
 
    /** How many links of the cause and getNextException() chains of a failure are looked at. */
    private static final int MAX_CHAIN_LENGTH = 32;
 
    /** What a connection string is cut down to where this cannot tell its credentials from the rest of it. */
    static final String CREDENTIALS_HIDDEN = "<credentials hidden>";
    /**
     * A password standing where neither the userinfo nor the parameters of a url are looked for.
     * The name goes by more than one spelling: mysql numbers the factors of a multi-factor login
     * (password1, password2), and a wallet or a key store carries one under a name of its own
     * (oracle.net.wallet_password, javax.net.ssl.keyStorePassword).
     * The value of one ends at a separator of a connection string or at the first space: a driver
     * is free to name a parameter in the middle of a sentence ("password=hunter2 for user u at
     * h:5432"), and a value class running to the end of the string would take the host, the port
     * and the cause of the failure into the blank along with the password.
     */
    private static final Pattern SECRET_PARAMETER =
        Pattern.compile("(?i)([\\w.]*(password|passwd|pwd)\\d*)\\s*=([^\\s,)&;?]*)");
 
    /** The parameters of a connection string worth keeping in a message: which database, not who connects. */
    private static final Set<String> IDENTIFYING_PARAMETERS = Collections.unmodifiableSet(new HashSet<>(
        Arrays.asList("databasename", "database", "instancename", "currentschema", "servicename")));
 
    // setNetworkTimeout() takes the executor its timeout handling runs on: the drivers it is used
    // with here only set a socket option in it, so it costs a call rather than a thread.
    private static final Executor DIRECT_EXECUTOR = Runnable::run;
 
    // Throttled per connection string: two JDBC backends stalling at once have a stall of their
    // own to report, and a single timestamp would let one of them starve the other. Keyed by the
    // safe form of it, the way warnedOnce below is: a static field of this class outlives every
    // borrow, and the password of the backend has no business in one.
    private static final Map<String, AtomicLong> lastStallWarning = new ConcurrentHashMap<>();
    private static final AtomicLong lastReadBoundWarning = new AtomicLong();
 
    /**
     * When an operation last reported that the database had dropped a connection of a pool, as a
     * {@link System#nanoTime()} reading per connection string. A connection proven alive before
     * that moment is validated on its next borrow whatever the window says: whatever dropped one
     * connection - a restart, a failover, a network that went away - dropped every connection
     * established before it, and the window would otherwise hand out the rest of that generation
     * one by one until the pool runs out of them. It is set by the caller that saw the failure
     * ({@code JDBCStorage}), never by a validation that failed here: an idle connection the server
     * reaped is a routine event, and it says nothing about the connection in use that the pool is
     * about to hand out.
     */
    private static final Map<String, Long> poolDistrustedAt = new ConcurrentHashMap<>();
 
    // Throttled like the stall warning above, and keyed the same way: the bound is one setting, so
    // one line per interval says so - but it is a setting of one pool, and two backends standing
    // full at once each have their own to report. A single timestamp would let the pool that
    // reported first silence the other, whose operations are failing with nothing in the log
    // naming the database behind them.
    private static final Map<String, AtomicLong> lastPoolFullWarning = new ConcurrentHashMap<>();
 
    final Connection parent;
 
    /** The pool this connection belongs to, held directly so that the return needs no lookup. */
    private final Pool pool;
    /** Whether this connection holds a permit of its pool: a reentrant borrow does not. */
    private final boolean metered;
    /**
     * The depth counter of the thread that borrowed it, lowered by the return. Held rather than the
     * thread itself: a return made on another thread has to lower the depth of the borrower all the
     * same, and a check of the returning thread against the borrowing one left that depth standing -
     * the borrower was then taken for a nested borrow for the life of the server, exempt from the
     * wait at the bound and opening an unmetered connection, destroyed on return, per operation
     * (issue #878).
     */
    private volatile AtomicInteger depth;
    /** When it was last returned to the pool, which is what the TTL is measured from. */
    volatile long returnedAtMillis;
    private final AtomicBoolean permitReleased = new AtomicBoolean();
    /** Whether it has been handed back already: JDBC makes close() on a closed connection a no-op. */
    private final AtomicBoolean returned = new AtomicBoolean();
 
    /** The pool of every connection string in use, kept until the last storage using it closes. */
    static final ConcurrentMap<String, Pool> pools = new ConcurrentHashMap<>();
 
    /** The sweep that closes connections nothing has borrowed for the TTL, started with the first pool. */
    private static volatile ScheduledExecutorService sweeper;
 
    /** 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 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.
     * <p>
     * Read on every borrow and every sweep rather than once, so that it can be changed on a running
     * server the way the bounds of a borrow can.
     */
    private static long getCacheTtlMillis() {
        return getNonNegativeProperty(TTL_PROPERTY, DEFAULT_TTL_MS, "ms");
    }
 
    /**
     * Returns the alive window, clamped to the {@value #TTL_PROPERTY} an idle pooled connection is
     * kept for and to {@link #MAX_ALIVE_BYPASS_MS} behind it. A window longer than the ttl is one
     * the pool cannot back: it goes on trusting the last answer of a connection past the point the
     * pool would have closed and replaced it, which is a claim about a connection that is no longer
     * there. The ttl has no upper bound of its own, though, so the second clamp is what keeps a
     * value the unit conversion saturates on from leaving every connection of the pool trusted for
     * the life of the server.
     * <p>
     * Read at class initialization, so a value of this property set after that does not change the
     * window. The ttl is not: {@link #getCacheTtlMillis()} is read on every borrow and every sweep,
     * and the clamp above is not applied again - the window keeps the value it was computed with,
     * so a ttl lowered on a running server does not lower the window with it.
     */
    static long getAliveBypassMillis() {
        long configured = getNonNegativeProperty(ALIVE_BYPASS_PROPERTY, DEFAULT_ALIVE_BYPASS_MS, "ms");
        final long ttl = getCacheTtlMillis();
        if (configured > ttl) {
            warnOnce(ALIVE_BYPASS_PROPERTY + "=" + configured + ">" + ttl,
                "The %s window of %d ms is longer than the %d ms of %s a pooled connection is kept for,"
                    + " and is used as %d ms: a connection trusted for longer than the pool keeps it would"
                    + " be trusted past the point the pool closed it",
                ALIVE_BYPASS_PROPERTY, configured, ttl, TTL_PROPERTY, ttl);
            configured = ttl;
        }
        if (configured > MAX_ALIVE_BYPASS_MS) { // the ttl it was just clamped to has no upper bound of its own
            warnOnce(ALIVE_BYPASS_PROPERTY + ">" + MAX_ALIVE_BYPASS_MS,
                "The %s window of %d ms is longer than the %d ms this pool trusts a connection for at most,"
                    + " and is used as %d ms: a longer one saturates the arithmetic it is compared in and"
                    + " leaves every connection of the pool trusted for the life of the server",
                ALIVE_BYPASS_PROPERTY, configured, MAX_ALIVE_BYPASS_MS, MAX_ALIVE_BYPASS_MS);
            return MAX_ALIVE_BYPASS_MS;
        }
        return configured;
    }
 
    /** The pool of a connection string, created on first use. */
    static Pool poolOf(String connectionString) {
        final Pool pool = pools.computeIfAbsent(connectionString, Pool::new);
        startSweeper();
        return pool;
    }
 
    private static void startSweeper() {
        if (sweeper != null) {
            return;
        }
        synchronized (pools) {
            if (sweeper == null) {
                // A thread per close in flight, and none while nothing is being closed. One thread
                // shared by all of them would only move the head of the line, which is the point
                // of not closing on the sweeper in the first place.
                closer = Executors.newCachedThreadPool(runnable -> {
                    final Thread thread = new Thread(runnable, "JDBC backend connection pool closer");
                    thread.setDaemon(true);
                    return thread;
                });
                final ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(runnable -> {
                    final Thread thread = new Thread(runnable, "JDBC backend connection pool sweeper");
                    thread.setDaemon(true);
                    return thread;
                });
                sweeper = service;
                // Rescheduled after each run rather than left at a fixed delay: the interval comes
                // from the ttl, and the ttl is read on every borrow and every sweep so that it can
                // be changed on a running server. A delay computed once would keep the sweeper of a
                // lowered ttl waking as rarely as the old one, so connections would go on being
                // reaped no sooner than the setting the operator replaced (issue #878).
                scheduleNextSweep(service);
            }
        }
    }
 
    /** Half the ttl, and no more often than {@value #MIN_SWEEP_INTERVAL_MS} ms. */
    private static long sweepIntervalMillis() {
        return Math.max(MIN_SWEEP_INTERVAL_MS, getCacheTtlMillis() / 2);
    }
 
    /**
     * Books the next sweep, and the one after it out of its own run. Every run books its successor
     * in a finally: a sweep that ends in a Throwable the per-pool guard did not catch would
     * otherwise stop the expiry of every pool in the JVM, the way a task thrown out of
     * scheduleWithFixedDelay does.
     */
    private static void scheduleNextSweep(ScheduledExecutorService service) {
        try {
            service.schedule(() -> {
                try {
                    sweep();
                } finally {
                    scheduleNextSweep(service);
                }
            }, sweepIntervalMillis(), TimeUnit.MILLISECONDS);
        } catch (RejectedExecutionException e) {
            // the sweeper is shutting down: there is nothing left to book a run on
            logger.traceException(e);
        }
    }
 
    // Expiry has to happen without a borrow behind it. Caffeine was left without a scheduler, so an
    // entry was only ever expired by a later cache operation - and a backend that has gone idle,
    // the one case the TTL exists for, performs none (issue #878).
    static void sweep() {
        final long ttlMillis = getCacheTtlMillis();
        final Executor closeOn = closer;
        for (final Pool pool : pools.values()) {
            try {
                pool.sweep(ttlMillis, closeOn);
            } catch (Throwable t) {
                // Error included: scheduleWithFixedDelay cancels a task that throws, so anything
                // escaping here would stop the expiry of every pool in the JVM for good - and
                // silently, which is the failure mode the hand-off of the close exists to avoid.
                logger.traceException(t);
            }
        }
    }
 
    /**
     * Registers a storage as a user of the pool of a connection string. Reference counted because a
     * pool belongs to a database rather than to a backend: two backends may address one database,
     * and closing one of them must not take the connections of the other with it.
     */
    static void openPool(String connectionString) {
        final Pool pool = poolOf(connectionString);
        pool.addUser();
        reportBoundBelowBorrowers(connectionString, pool);
    }
 
    /**
     * Reports a bound smaller than the number of worker threads. An operation borrows one connection
     * for its duration, so the worker threads are the borrowers this default is sized against - and
     * it is sized against the count the server computes for itself, not against a
     * {@code ds-cfg-num-worker-threads} the operator set, which replaces that count outright.
     * <p>
     * A lower bound than that is what is reported, not every way past it: the replay threads of
     * replication default to the same count again and borrow on top of the workers, and an import or
     * a rebuild borrows besides. So this names one difference the operator can act on rather than
     * standing for the whole demand on the pool.
     * <p>
     * Nothing fails for the difference alone: the surplus waits for a connection to be returned,
     * which is what the bound is there for. But every one of those waits is paid on an operation,
     * and past {@value #POOL_TIMEOUT_PROPERTY} the operation fails - on a setting whose effect on
     * this backend the operator had no reason to expect (issue #878).
     */
    private static void reportBoundBelowBorrowers(String connectionString, Pool pool) {
        final WorkQueue<?> workQueue = DirectoryServer.getWorkQueue();
        if (workQueue == null) {
            // an offline tool, or the server before its work queue is up: no borrowers to count
            return;
        }
        final int borrowers = workQueue.getNumWorkerThreads();
        if (borrowers <= pool.max()) {
            return;
        }
        final long poolTimeoutSeconds = getNonNegativeProperty(POOL_TIMEOUT_PROPERTY, DEFAULT_POOL_TIMEOUT_SECONDS, "s");
        final String wait = poolTimeoutSeconds == 0
            ? "waits for one to be returned for as long as that takes"
            : "waits up to " + poolTimeoutSeconds + "s for one to be returned and fails if none is";
        warnOnce(safeUrl(connectionString) + "|bound-below-borrowers",
            "the connection pool of %s holds at most %d connections while %d worker threads may each borrow one:"
                + " an operation finding it at its bound %s (raise %s to allow more connections, or lower"
                + " ds-cfg-num-worker-threads)",
            safeUrl(connectionString), pool.max(), borrowers, wait, POOL_MAX_PROPERTY);
    }
 
    /** Unregisters a storage; the connections are released once the last user is gone. */
    static void closePool(String connectionString) {
        final Pool pool = pools.get(connectionString);
        if (pool != null) {
            pool.removeUser();
        }
    }
 
    /**
     * The connections of one connection string.
     * <p>
     * This replaces the cache entry that used to hold them. That one carried the TTL on the pool
     * rather than on a connection - {@code expireAfterAccess} keyed by the connection string, reset
     * by every borrow and every return - so under continuous traffic nothing ever expired and the
     * peak count of a burst stayed open for as long as the backend saw any traffic at all. It also
     * had no bound, so the only ceiling on the connections of a backend was the {@code
     * max_connections} of the database itself (issue #878).
     */
    static final class Pool {
        final String connectionString;
        /** Idle connections, most recently returned first: the ones a burst opened sink to the bottom, where the sweep finds them. */
        private final LinkedBlockingDeque<CachedConnection> idle = new LinkedBlockingDeque<>();
        /** One permit per live connection, borrowed or idle. Sized once: this is how large the pool may grow, not a rate. */
        private final Semaphore permits;
        private final int max;
        /**
         * How many connections of this pool the current thread holds. A borrow made while one is
         * already held may exceed the bound, because the two are held at the same time and waiting
         * for the first to be returned would wait for this very thread:
         * {@code PersistentCompressedSchema.store()} opens a write of its own - the definition has
         * to commit independently of the entry - and {@code EntryContainer.modifyDN} reaches it
         * from inside a transaction, having encoded the entry there. The exemption is from the
         * wait rather than from the pool: a nested borrow served out of the idle deque carries the
         * permit that connection already holds and is pooled again on return like any other. Only
         * one that had to establish a connection of its own, because the pool stood at its bound,
         * holds no permit - and that one is closed rather than pooled when it comes back, so the
         * pool does not grow past its bound.
         * <p>
         * Counted per pool rather than per thread, because that deadlock only exists within one
         * pool: a count shared by all of them would judge a thread holding a connection to one
         * database reentrant while it borrows from another, passing the bound of a pool it holds
         * nothing of and destroying the connection instead of pooling it, on every operation.
         */
        private final ThreadLocal<AtomicInteger> held = ThreadLocal.withInitial(AtomicInteger::new);
        /** Open storages using this pool, guarded by this. */
        private int users;
        /**
         * Set when the last storage using this pool closed. A pool no storage ever registered with -
         * a borrow made straight through {@link CachedConnection#getConnection}, as the tests do -
         * is not closed and pools normally; only one that had a user and lost it stops keeping
         * connections for a borrower that is not going to come.
         */
        private volatile boolean closed;
 
        Pool(String connectionString) {
            this.connectionString = connectionString;
            final long configured = getNonNegativeProperty(POOL_MAX_PROPERTY, DEFAULT_POOL_MAX, "connections");
            this.max = (configured == 0 || configured > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) configured;
            this.permits = new Semaphore(max);
        }
 
        int max() {
            return max;
        }
 
        /** Whether the calling thread already holds a connection of this pool. */
        boolean heldByCurrentThread() {
            return held.get().get() > 0;
        }
 
        /**
         * Raises the depth of the borrowing thread and hands back the counter it was raised on, for
         * the connection to lower on its return. The counter rather than the thread, because the
         * return need not happen on the thread that borrowed - and the depth that has to come down
         * is the borrower's either way. Read by that thread alone but written by whichever returns
         * the connection, which is why it is an AtomicInteger and not an int.
         */
        AtomicInteger enter() {
            final AtomicInteger depth = held.get();
            depth.incrementAndGet();
            return depth;
        }
 
        /** Lowers a depth this pool handed out, never below zero. */
        static void leave(AtomicInteger depth) {
            depth.updateAndGet(held -> held > 0 ? held - 1 : 0);
        }
 
        int idleCount() {
            return idle.size();
        }
 
        /**
         * The connections of this pool holding a permit, borrowed and idle together. Not every
         * connection of the pool: a borrow nested in one this thread already holds goes on
         * unmetered when the pool stands at its bound, so the connections this count misses are
         * exactly the ones over the bound. They take no place in it and are closed rather than
         * pooled when they come back, which makes this the count the bound is about - how much of
         * it is taken - rather than the number of sockets open to the database.
         */
        int meteredCount() {
            return max - permits.availablePermits();
        }
 
        synchronized void addUser() {
            users++;
            closed = false;
        }
 
        void removeUser() {
            final boolean wasLast;
            synchronized (this) {
                wasLast = users > 0 && --users == 0;
                if (wasLast) {
                    closed = true;
                }
            }
            if (wasLast) {
                // Outside the monitor: closing a connection is a round trip, and an open of the
                // same database has no reason to wait behind it. The borrowed ones are not here to
                // be closed - give() closes them when they come back, since a pool nobody uses must
                // not keep them for a borrower that is not going to come.
                logger.trace(LocalizableMessage.raw("releasing %d pooled connections of %s: its last user closed",
                    idle.size(), safeUrl(connectionString)));
                drainIdle();
            }
        }
 
        void drainIdle() {
            for (CachedConnection con = idle.pollFirst(); con != null; con = idle.pollFirst()) {
                destroy(con);
            }
        }
 
        /**
         * Takes a connection out of the pool, waiting up to waitMs for one to be returned, and
         * discarding the ones that are broken or have been idle for longer than the TTL.
         * <p>
         * Bounded by the deadline of the borrow, and not only by waitMs: a poll of no duration
         * still hands out whatever the deque holds, and discarding a connection whose socket is
         * half-open costs the validation timeout apiece. The pool holds as many of those as its
         * bound allows, so draining the deque overran the bound the operator set - by minutes on a
         * large pool, before the connect that follows it had even started (issue #878).
         */
        CachedConnection pollIdle(long waitMs, long ttlMillis, long deadline, boolean trusted)
                throws InterruptedException {
            long remainingWait = waitMs;
            while (true) {
                final long polledAt = System.currentTimeMillis();
                final CachedConnection con = idle.pollFirst(remainingWait, TimeUnit.MILLISECONDS);
                if (con == null) {
                    return null;
                }
                if (System.currentTimeMillis() - con.returnedAtMillis <= ttlMillis && isUsable(con, trusted)) {
                    return con;
                }
                destroy(con);
                final long remaining = deadline - System.currentTimeMillis();
                if (remaining <= 0) {
                    return null;
                }
                // one more look, since a connection may have been returned in the meantime
                remainingWait = Math.min(Math.max(0, remainingWait - (System.currentTimeMillis() - polledAt)), remaining);
            }
        }
 
        /** Takes the right to hold one more connection, or reports that the pool is full. */
        boolean tryReserve() {
            return permits.tryAcquire();
        }
 
        void cancelReservation() {
            permits.release();
        }
 
        /** Hands a connection back, closing it rather than pooling it when it may not be kept. */
        void give(CachedConnection con) {
            // An unmetered connection holds no permit, so pooling it would put the pool one over its
            // bound for good; and a closed pool has nobody left to hand it to.
            if (con.metered && !closed) {
                addIdle(con);
                if (closed) {
                    // The last user left while this one was on its way back, so it missed the drain.
                    drainIdle();
                }
            } else {
                destroy(con);
            }
        }
 
        /** Puts a connection into the pool. The caller must hold the right to keep it there. */
        void addIdle(CachedConnection con) {
            con.returnedAtMillis = System.currentTimeMillis();
            idle.addFirst(con);
        }
 
        void destroy(CachedConnection con) {
            try {
                closeQuietly(con.parent);
            } finally {
                // However the close went, the pool holds one connection fewer. A permit not given
                // back here is given back by nothing at all: only a live connection carries one,
                // and this one is gone (issue #878).
                con.releasePermit();
            }
        }
 
        void sweep(long ttlMillis) {
            sweep(ttlMillis, DIRECT_EXECUTOR);
        }
 
        /**
         * Closes the connections nothing has borrowed for the TTL, handing each to the executor
         * given rather than closing it here. The sweep of every pool shares one thread and
         * {@code scheduleWithFixedDelay} never overlaps its runs, so one close that does not
         * return would stop the expiry of every pool in the JVM - and silently, since only a
         * thrown exception is logged. Oracle logs off over the network, and the read bound of the
         * login has been lifted by then (issue #878).
         */
        void sweep(long ttlMillis, Executor closeOn) {
            final long deadline = System.currentTimeMillis() - ttlMillis;
            // From the tail: the least recently returned connection is the first to have expired,
            // and once one has not, neither has anything in front of it.
            for (CachedConnection con = idle.peekLast(); con != null; con = idle.peekLast()) {
                if (con.returnedAtMillis > deadline) {
                    return;
                }
                if (!idle.removeLastOccurrence(con)) {
                    // A borrow took it between the two. What is behind it may still have expired,
                    // and ending the cycle here would leave every one of those open until the
                    // next sweep.
                    continue;
                }
                if (con.returnedAtMillis > deadline) {
                    // A borrow took it between the peek and the removal and gave it back, so the
                    // reading the decision was made on is not the one it carries now: closing it
                    // would cost the next borrow a connect over a connection a moment old. Back to
                    // the end it is returned to, where its refreshed reading belongs.
                    idle.addFirst(con);
                    return;
                }
                final CachedConnection expired = con;
                try {
                    closeOn.execute(() -> destroy(expired));
                } catch (RuntimeException e) { // no thread to close it on: here rather than nowhere
                    destroy(expired);
                }
            }
        }
    }
 
    /**
     * Returns the value of a numeric system property, ignoring a value that is not a non-negative
     * number in favor of the default. The unit is the one the property is read in, so that the
     * value the message names is not mistaken for another.
     */
    private static long getNonNegativeProperty(String name, long defaultValue, String unit) {
        final String value = System.getProperty(name);
        if (value != null) {
            try {
                final long parsed = Long.parseLong(value.trim());
                if (parsed >= 0) {
                    return parsed;
                }
            } catch (NumberFormatException ignored) {
            }
            // reported once for this value: both properties are read on every borrow, so a
            // "30s" of a typo would otherwise put two lines in the log per backend operation
            warnOnce(name + "=" + value, "Ignoring invalid value \"%s\" of the %s property, using %d %s",
                value, name, defaultValue, unit);
        }
        return defaultValue;
    }
 
    /** Reports something about a setting once for the life of the jvm, however many borrows meet it. */
    private static void warnOnce(String key, String format, Object... args) {
        if (warnedOnce.add(key)) {
            logger.warn(LocalizableMessage.raw(format, args));
        }
    }
 
    /**
     * The drivers this backend is used with, recognized by the prefix of the connection string,
     * together with the properties that bound one attempt to establish a connection. Not one of
     * them bounds the attempt with a single property: the one named first covers the socket
     * connect, and the login behind it - the reads of the prelogin handshake, of TLS and of
     * authentication, the phase a proxy at its connection limit or a moved VIP leaves unanswered -
     * needs the read bound behind it. That holds for the SQL Server driver too, whose loginTimeout
     * leaves the read of the prelogin answer unbounded - and for pgjdbc, whose loginTimeout is not
     * a bound of the socket at all: Driver.connect hands the login to a daemon thread of its own
     * and abandons it at the timeout, so an unbounded read there leaks a thread and a socket per
     * borrow instead of failing one (CachedConnectionTestCase covers every one of them against a
     * socket that never answers).
     */
    enum ConnectDialect {
        /**
         * postgresql: every property of the three takes seconds. connectTimeout covers the socket
         * connect and socketTimeout the reads of the login: pgjdbc puts an SO_TIMEOUT on the login
         * socket only where socketTimeout is set (ConnectionFactoryImpl.tryConnect, both before and
         * after enableSSL), and it defaults to none. loginTimeout is kept on top of the two for a
         * url naming more than one host, where each of them costs a login of its own - the connect
         * is one budget for all of them, taken from the single System.nanoTime() in front of the
         * loop over the hosts - but it is not a bound this class could rely on alone: Driver.connect
         * runs the login on a daemon thread, gives up on the thread rather than on the login, and
         * the thread stays parked in the read for as long as the read lasts.
         */
        POSTGRES("jdbc:postgresql:", '?',
            new String[]{"connectTimeout", "loginTimeout"}, 1, 0,
            new String[]{"socketTimeout"}, 1, true,
            new int[]{}, new int[]{}),
        /** mysql: both properties take milliseconds; socketTimeout is a socket read timeout that outlives the login. */
        MYSQL("jdbc:mysql:", '?',
            new String[]{"connectTimeout"}, 1000, 0,
            new String[]{"socketTimeout"}, 1000, true,
            new int[]{1040, 1203}, new int[]{1053}),
        /** oracle: both properties take milliseconds; ReadTimeout is a socket read timeout that outlives the login. */
        ORACLE("jdbc:oracle:", '?',
            new String[]{"oracle.net.CONNECT_TIMEOUT"}, 1000, 0,
            // the read bound goes by two names the driver reads: the property set here and the
            // property of oracle net it stands for, inside a tns descriptor by the last segment of
            // either. A bound under one of them is a bound of the administrator, so ours is not set
            // on top of it - and neither of theirs is lifted with ours once the login is through.
            // Only the first of the two is a name the driver also reads out of the system
            // properties (SYSTEM_PROPERTY_NAMES below): oracle.net.READ_TIMEOUT reaches the socket
            // from the connection properties alone, so a -D of it bounds nothing and must not be
            // taken for a bound of theirs.
            // RECV_TIMEOUT is not one of them: it is a parameter of sqlnet.ora and of the listener,
            // and the name does not appear in ojdbc8 at all, so a descriptor carrying one would
            // have taken our bound off a connection that never had one of its own.
            new String[]{"oracle.jdbc.ReadTimeout", "oracle.net.READ_TIMEOUT"}, 1000, true,
            // ORA-01033 and ORA-01034: the instance is starting up or not there yet; ORA-01089:
            // it is shutting down. ORA-12514 is left out of these on purpose - a listener that
            // does not know the service is also what a service name of a typo looks like, forever
            new int[]{20, 12516, 12518, 12519, 12520}, new int[]{1033, 1034, 1089}),
        /**
         * ms sql server: loginTimeout takes seconds, socketTimeout milliseconds; the latter is a
         * socket read timeout that outlives the login. loginTimeout is the one property of the
         * four with a range of its own - SQLServerDriverIntProperty.LOGIN_TIMEOUT is validated
         * against [0, 65535], and a value beyond it fails every connect the driver is asked for.
         */
        MICROSOFT("jdbc:sqlserver:", ';',
            new String[]{"loginTimeout"}, 1, 65535,
            new String[]{"socketTimeout"}, 1000, true,
            // 921 and 922: the database has not been recovered yet, or is being recovered; 927: it
            // is in the middle of a restore; 40613: azure sql reporting it not available for now
            new int[]{17809, 10928, 10929}, new int[]{921, 922, 927, 40613});
 
        final String urlPrefix;
        /** the character that separates the parameters of this dialect from the url in front of them */
        final char parameterSeparator;
        /** the properties bounding the connect: the socket connect, and whatever the driver wraps it in */
        final String[] connectProperties;
        final int connectUnitsPerSecond;
        /** the largest value the driver accepts for a connect property, 0 for a driver that takes any */
        final long maxConnectSeconds;
        /** the read bound of the login: the first name is the one set here, the rest are the names it also goes by */
        final String[] readProperties;
        final int readUnitsPerSecond;
        /** whether the read bound of the login stays in force for every statement issued afterwards */
        final boolean readBoundOutlivesLogin;
        /** the vendor codes of this dialect for "no further connection is accepted" */
        final int[] connectionLimitCodes;
        /** the vendor codes of this dialect for "not accepting connections yet": a database on its way up */
        final int[] notAcceptingYetCodes;
 
        ConnectDialect(String urlPrefix, char parameterSeparator,
                       String[] connectProperties, int connectUnitsPerSecond, long maxConnectSeconds,
                       String[] readProperties, int readUnitsPerSecond, boolean readBoundOutlivesLogin,
                       int[] connectionLimitCodes, int[] notAcceptingYetCodes) {
            this.urlPrefix = urlPrefix;
            this.parameterSeparator = parameterSeparator;
            this.connectProperties = connectProperties;
            this.connectUnitsPerSecond = connectUnitsPerSecond;
            this.maxConnectSeconds = maxConnectSeconds;
            this.readProperties = readProperties;
            this.readUnitsPerSecond = readUnitsPerSecond;
            this.readBoundOutlivesLogin = readBoundOutlivesLogin;
            this.connectionLimitCodes = connectionLimitCodes;
            this.notAcceptingYetCodes = notAcceptingYetCodes;
        }
 
        /** The dialect of a connection string, or null for a driver whose property names are not known here. */
        static ConnectDialect of(String connectionString) {
            final String url = connectionString.toLowerCase(Locale.ROOT);
            for (final ConnectDialect dialect : values()) {
                if (url.startsWith(dialect.urlPrefix)) {
                    return dialect;
                }
            }
            return null;
        }
 
        /**
         * Fills in the properties bounding one connect attempt, leaving out what the administrator
         * bounded themselves - an explicit setting of theirs keeps precedence, on the SQL Server,
         * mysql and oracle drivers because a supplied property outranks the url, and on postgresql
         * because the url outranks the property. A driver with a range of its own for its connect
         * property is not handed a value beyond it: a bound it rejects is no bound at all, it is a
         * connect that never happens.
         * Returns whether a read bound outliving the login was set and has to be lifted once the
         * connection is established.
         */
        boolean bound(String connectionString, Properties properties, long timeoutSeconds) {
            final long connectSeconds = maxConnectSeconds > 0
                ? Math.min(timeoutSeconds, maxConnectSeconds) : timeoutSeconds;
            // The connect side is one budget rather than a set of independent knobs, so a bound of
            // the administrator under any of its names leaves all of them alone. On postgresql
            // connectTimeout bounds the socket connect and loginTimeout the login behind it:
            // filling in the one they left out caps the one they set, and a "?connectTimeout=300"
            // answered with a loginTimeout of ours is a login pgjdbc gives up on at 30 s - Driver
            // .connect branches into its own thread as soon as loginTimeout is anything but 0.
            if (!declared(connectionString, connectProperties)) {
                for (final String property : connectProperties) {
                    properties.setProperty(property, Long.toString(connectSeconds * connectUnitsPerSecond));
                }
            }
            reportBoundTurnedOffInUrl(connectionString);
            if (!declared(connectionString, readProperties)) {
                properties.setProperty(readProperties[0], Long.toString(timeoutSeconds * readUnitsPerSecond));
                return readBoundOutlivesLogin;
            }
            return false;
        }
 
        /**
         * Reports a url that turns a bound off where nothing this class supplies can put one back.
         * A parameter of a postgresql url outranks the property this class hands the driver, so a
         * "socketTimeout=0" there is not a default to be replaced - it is the administrator asking
         * for an unbounded read, and a borrow that meets a database accepting the connection and
         * answering nothing is then parked with no deadline able to reach it.
         */
        private void reportBoundTurnedOffInUrl(String connectionString) {
            if (!urlOutranksProperties()) {
                return;
            }
            // Every one of them, and keyed by the property rather than by the url alone: a url
            // turns off the read bound and the login bound both ("?socketTimeout=0&loginTimeout=0",
            // where the second is the per-host budget of a failover url), and safeUrl() keeps none
            // of the timeout parameters - so a single key would report the first offender and
            // leave the administrator to find the rest of them on their own.
            for (final String[] properties : new String[][]{readProperties, connectProperties}) {
                for (final String property : properties) {
                    final String value = parameterValue(connectionString, property);
                    if (value != null && !isBound(value)) {
                        warnOnce(safeUrl(connectionString) + "|unbounded|" + property,
                            "%s sets \"%s=%s\": a parameter of a postgresql url outranks the property this backend"
                                + " supplies, so that phase of a connect carries no bound. An operation reaching a"
                                + " database that accepts the connection and does not answer stays parked",
                            safeUrl(connectionString), property, value);
                    }
                }
            }
        }
 
        /** Whether a vendor code of this dialect is one that waiting for the database can clear. */
        boolean isWorthRetrying(int errorCode) {
            return contains(connectionLimitCodes, errorCode) || contains(notAcceptingYetCodes, errorCode);
        }
 
        private static boolean contains(int[] codes, int code) {
            for (final int candidate : codes) {
                if (candidate == code) {
                    return true;
                }
            }
            return false;
        }
 
        // Whether the administrator bounded one of these properties themselves. The dialects
        // separate their parameters differently - "?a=1&b=2" (postgresql, mysql), ";a=1;b=2" (sql
        // server), "(A=1)" inside the descriptor of an oracle tns url, where the property also goes
        // by the last segment of its name alone - so a parameter is recognized by the delimiter in
        // front of it and the "=" behind it rather than by parsing the url syntax of every driver.
        // The connection string is not the only channel of theirs: the oracle driver reads some of
        // its properties out of the system properties as well, which is how a whole jvm is bounded
        // with -Doracle.jdbc.ReadTimeout, and a property supplied to a driver outranks the system
        // property without a word - and would then be lifted after the login as if it were ours,
        // leaving a connection with no read bound where the administrator had set one.
        private boolean declared(String connectionString, String... properties) {
            for (final String property : properties) {
                if (declaredInUrl(connectionString, property) || setAsSystemProperty(property)) {
                    return true;
                }
            }
            return false;
        }
 
        /** Whether the connection string bounds this property, under its own name or the last segment of it. */
        private boolean declaredInUrl(String connectionString, String property) {
            if (containsParameter(connectionString, property)) {
                return true;
            }
            final int dot = property.lastIndexOf('.');
            return dot >= 0 && containsParameter(connectionString, property.substring(dot + 1));
        }
 
        // Which names a driver reads out of the system properties, listed rather than told from
        // the shape of the name: ojdbc8 resolves oracle.jdbc.ReadTimeout and
        // oracle.net.CONNECT_TIMEOUT in three tiers (the properties it was supplied, then
        // System.getProperty, then the properties of the data source), while
        // oracle.net.READ_TIMEOUT - a dotted name of the same driver - is read out of the
        // connection properties alone: the six classes carrying the literal hand it to
        // Properties.get, and none of them to System.getProperty. Taking a -D of it for a bound of
        // the administrator would leave the login with no read bound at all - theirs not read by
        // the driver and ours not set, because we believed theirs was in force.
        private static final Set<String> SYSTEM_PROPERTY_NAMES = Collections.unmodifiableSet(new HashSet<>(
            Arrays.asList("oracle.jdbc.ReadTimeout", "oracle.net.CONNECT_TIMEOUT")));
 
        private static boolean setAsSystemProperty(String property) {
            return SYSTEM_PROPERTY_NAMES.contains(property) && isBound(System.getProperty(property));
        }
 
        // pgjdbc parses the url over the properties it was handed - Driver.connect copies them
        // into a flat map and parseURL then writes the parameters of the url on top - so a value
        // standing in a postgresql url is the value the driver uses, and the one supplied here
        // never reaches the socket. A zero there is not a default of the driver to be replaced: it
        // cannot be replaced, and setting ours on top of it would leave this class lifting a read
        // bound the login never had. The other three let a supplied property win, so a zero of
        // theirs is ours to override.
        private boolean urlOutranksProperties() {
            return this == POSTGRES;
        }
 
        /** Whether this property is bounded by the connection string, as the driver of this dialect reads it. */
        private boolean containsParameter(String connectionString, String property) {
            final String value = parameterValue(connectionString, property);
            return value != null && (urlOutranksProperties() || isBound(value));
        }
 
        // Matched the way the driver of this dialect matches it: pgjdbc and Connector/J look their
        // properties up by their exact name - PropertyKey.fromValue answers null for a name of
        // another case and the parameter is then a parameter of nobody, so "?SocketTimeout=" must
        // not be taken for a bound of the administrator - while the SQL Server driver
        // (getNormalizedPropertyName) and the keywords of an oracle descriptor match either way.
        private String parameterValue(String connectionString, String property) {
            final boolean exact = this == POSTGRES || this == MYSQL;
            final String url = exact ? connectionString : connectionString.toLowerCase(Locale.ROOT);
            final String name = exact ? property : property.toLowerCase(Locale.ROOT);
            String value = null;
            for (int i = url.indexOf(name); i >= 0; i = url.indexOf(name, i + name.length())) {
                final int end = i + name.length();
                if (i > 0 && "?&;(,".indexOf(url.charAt(i - 1)) >= 0 && end < url.length() && url.charAt(end) == '=') {
                    // the last of them: a driver parsing a url into a map lets the last assignment stand
                    value = valueOf(url, end + 1);
                }
            }
            return value;
        }
 
        /** The value of the parameter that starts here: up to the delimiter in front of the next one. */
        private static String valueOf(String url, int from) {
            int end = from;
            while (end < url.length() && "&;),?".indexOf(url.charAt(end)) < 0) {
                end++;
            }
            return url.substring(from, end);
        }
 
        /**
         * Whether a value of the administrator bounds anything. Every one of these drivers reads 0
         * as "wait as long as it takes", so a property set to it is not a bound of theirs to stay
         * out of the way of - it is the default this class exists to replace, and one of ours goes
         * on top of it wherever a supplied property outranks the url. Where it does not, on
         * postgresql, the zero stands and is reported instead of being written over. A value that
         * is no number is left to the driver it belongs to.
         */
        private static boolean isBound(String value) {
            if (value == null || value.trim().isEmpty()) {
                return false;
            }
            try {
                return Double.parseDouble(value.trim()) != 0; // pgjdbc takes a float for its loginTimeout
            } catch (NumberFormatException notANumber) {
                return true;
            }
        }
    }
 
    final String connectionString;
    /**
     * Whether this connection may go back into the pool once it is closed. A connection carrying a
     * read bound that could not be lifted serves the borrower waiting for it and is closed
     * afterwards: left in the pool it would fail every statement slower than that bound - an
     * import batch among them - for every borrow the pool hands it to.
     */
    private final boolean poolable;
 
    /**
     * When this connection last answered the database, as a {@link System#nanoTime()} reading:
     * established - the login and the two round trips that set it up have just answered - or
     * validated. It is never stamped on the way back into the pool, although that is where a
     * connection has most recently been used: {@link #close()} ends the transaction, and pgjdbc
     * short-circuits both {@code rollback()} and {@code commit()} when the transaction state is
     * IDLE, so on a borrow that issued no statement - {@code JDBCStorage.open()}, a configuration
     * change that leaves the base DNs alone, an import of nothing - not a byte reaches the server
     * and the stamp would prove nothing, while marking a connection the database may have dropped
     * as the freshest one in the pool. Stamping proof rather than use makes the window mean
     * "validated at most once per window", which is a claim this class can always back.
     * <p>
     * It stands for the moment the connection was <em>asked</em>, not the moment its answer was
     * filed: {@link #distrustPool} is compared against it as an ordering of two moments, and a
     * proof that took a second to arrive would otherwise outlive a drop reported while it was
     * still in flight. Reading it early only ever ages the proof, which costs a validation and
     * never skips one.
     */
    private volatile long lastKnownAliveNanos;
 
    /**
     * A connection outside the accounting of its pool: it holds no permit and is never pooled - the
     * flag says so as well as the accounting does, since a connection holding no permit is closed
     * by {@link Pool#give} rather than kept whatever the flag says.
     * <p>
     * It still names a pool, because that is what closes it and what the sweep runs over, so the
     * pool of this connection string is created here if it does not exist yet and the sweeper is
     * started with it.
     */
    public CachedConnection(String connectionString, Connection parent) {
        this(connectionString, parent, poolOf(connectionString), false, false);
    }
 
    CachedConnection(String connectionString, Connection parent, Pool pool, boolean metered, boolean poolable) {
        this.connectionString = connectionString;
        this.parent = parent;
        this.pool = pool;
        this.metered = metered;
        this.poolable = poolable;
        this.lastKnownAliveNanos = System.nanoTime();
    }
 
    /** Gives back the right to hold this connection, once and only if it was taken. */
    void releasePermit() {
        if (metered && permitReleased.compareAndSet(false, true)) {
            pool.cancelReservation();
        }
    }
 
    /** Records that the borrowing thread holds this connection, so a borrow nested in it is recognized. */
    private static CachedConnection borrowed(CachedConnection con) {
        con.returned.set(false);
        con.depth = con.pool.enter();
        return con;
    }
 
    /**
     * Borrows a connection: a usable one out of the pool, or a newly established one. Bounded in
     * both phases - every operation of this backend, the open of a backend and the import
     * included, comes through here, and an unbounded borrow turns a database that listens but does
     * not answer into a hang rather than into an error the caller can report.
     */
    static Connection getConnection(String connectionString) throws Exception {
        return getConnection(connectionString, true);
    }
 
    /**
     * Borrows a connection, either trusting the alive window of {@value #ALIVE_BYPASS_PROPERTY} or
     * validating whatever comes out of the pool.
     *
     * @param trusted false for a borrow nothing compensates a dropped connection on. What the
     * window trades away is the connection that breaks inside it, and {@code JDBCStorage} takes
     * that off the caller where it can - a write is replayed, a read tells the pool - but the
     * borrows that open a backend, remove its files or start an import have neither: they issue
     * their statements far from the borrow, and the one that opens a backend issues none at all,
     * so a dropped connection would surface out of the {@code rollback()} of its release. Each of
     * them is one borrow of a cold path, where the round trip the window saves is worth nothing.
     */
    static Connection getConnection(String connectionString, boolean trusted) throws Exception {
        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 ttlMillis = getCacheTtlMillis();
        final long startedAt = System.currentTimeMillis();
        final long deadline = (poolTimeoutSeconds == 0 || poolTimeoutSeconds >= Long.MAX_VALUE / 1000)
            ? Long.MAX_VALUE : startedAt + poolTimeoutSeconds * 1000;
        // 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();
        long waitMs = 0;
        long backoffMs = 0;
        int attempts = 0;
        while (true) {
            final CachedConnection pooled = pool.pollIdle(waitMs, ttlMillis, deadline, trusted);
            if (pooled != null) {
                return borrowed(pooled);
            }
            // Asked for whether this borrow is nested or not: the exemption a nested one carries is
            // from the wait, not from the pool. A nested borrow made while the pool has room takes a
            // permit like any other and is pooled again on return; only one that finds the pool at its
            // bound goes on unmetered, and that one is closed rather than pooled when it comes back.
            final boolean metered = pool.tryReserve();
            if (!metered && !reentrant) {
                // The pool holds as many connections as it may: only a returned one can serve this
                // borrow now, and the deadline decides how long that is worth waiting for. This is
                // the point of the bound - without it the borrow would open one more connection,
                // and the only ceiling left would be the max_connections of the database itself.
                final long remaining = deadline - System.currentTimeMillis();
                if (remaining <= 0) {
                    // The restart is part of the remedy, so the message says so: the bound is read
                    // once, when the pool is created, and a pool is never removed from the map - so
                    // the property set on a running server changes nothing until it is read again.
                    final String message = "no connection to " + safeUrl(connectionString)
                        + " could be borrowed within " + poolTimeoutSeconds + "s: all " + pool.max()
                        + " connections of the pool are in use (raise " + POOL_MAX_PROPERTY
                        + " and restart the server to allow more)";
                    // The one failure the bound introduces has to reach the server log too: an
                    // installation whose peak sits above the default would otherwise see its
                    // operations fail with nothing in the log naming the pool behind it.
                    warnPoolFull(connectionString, message);
                    throw new SQLTimeoutException(message);
                }
                waitMs = Math.min(POOL_FULL_POLL_MS, remaining);
                continue;
            }
            attempts++;
            CachedConnection established = null;
            boolean handedOff = false;
            try {
                established = connect(connectionString, dialect,
                    attemptSeconds(connectTimeoutSeconds, deadline), pool, metered);
                final CachedConnection con = borrowed(established);
                handedOff = true;
                return con;
            } catch (SQLException e) {
                // A database that takes no connection for the moment is the failure worth waiting
                // out: it is at its connection limit, and one of ours is going to come back to the
                // pool - or it is on its way up, and the state clears itself in seconds. Everything
                // else - a password that is not accepted, a database that is down, a driver that is
                // not on the classpath - is reported to the caller instead of being retried behind
                // its back.
                if (!isWorthRetrying(e, dialect)) {
                    throw reported(e, connectionString);
                }
                final long remaining = deadline - System.currentTimeMillis();
                if (remaining <= 0) {
                    // 08001, the state of a connect that did not happen, rather than none at all:
                    // this is the failure of a borrow, and a caller reading the state of what it
                    // caught would otherwise see null where the driver's own exception carried one
                    final SQLTimeoutException timeout = new SQLTimeoutException("no connection to "
                        + safeUrl(connectionString) + " could be borrowed within " + poolTimeoutSeconds + "s ("
                        + attempts + " attempts): the database took no connection for the moment and none was"
                        + " returned to the pool, last error: " + redact(e.getMessage(), connectionString),
                        CONNECT_FAILED_SQL_STATE);
                    timeout.initCause(reported(e, connectionString));
                    throw timeout;
                }
                backoffMs = Math.min(backoffMs == 0 ? 1 : backoffMs * 2, MAX_BACKOFF_MS);
                waitMs = Math.min(backoffMs, remaining);
                warnStall(connectionString, attempts, startedAt, e);
            } catch (RuntimeException e) {
                // a driver reporting a connect it will not make as an unchecked failure carries the
                // connection string of the backend in its message as readily as a SQLException does
                throw reportedUnchecked(e, connectionString);
            } finally {
                // What the attempt took is given back on every way out of it, not only on the
                // SQLException a driver is supposed to throw. DriverManager catches SQLException
                // alone, so an unchecked failure of a driver reaches here - Connector/J hands a url
                // with a "%" in it to URLDecoder, and this backend keeps its credentials in the url
                // - and a permit left behind is left behind for good: only a live connection
                // carries one, and a failed attempt has none to give (issue #878).
                if (!handedOff) {
                    if (established != null) {
                        pool.destroy(established); // the permit went with it, and comes back with it
                    } else if (metered) {
                        pool.cancelReservation();
                    }
                }
            }
        }
    }
 
    /**
     * Reports a connection string this class knows no bound for. The properties bounding a connect
     * are the ones of a driver, so a driver outside the four leaves every attempt unbounded - and
     * the deadline of the borrow cannot reach into a connect that is already under way, since the
     * driver is the only thing holding the socket.
     */
    private static void reportUnknownDialect(String connectionString, ConnectDialect dialect) {
        if (dialect != null) {
            return;
        }
        final StringBuilder known = new StringBuilder();
        for (final ConnectDialect candidate : ConnectDialect.values()) {
            known.append(known.length() > 0 ? ", " : "").append(candidate.urlPrefix);
        }
        warnOnce(safeUrl(connectionString) + "|unknown-dialect",
            "%s names a driver whose timeout properties are not known to this backend (%s are): a connect to a"
                + " database that accepts it and does not answer is left without a bound, and the %s property"
                + " cannot end it",
            safeUrl(connectionString), known, POOL_TIMEOUT_PROPERTY);
    }
 
    /**
     * The bound of one connect attempt. The deadline of the borrow bounds it as well - the
     * {@value #POOL_TIMEOUT_PROPERTY} property stands for the whole borrow, and an attempt of its
     * own left to run out would overrun it by a full connect timeout. That holds for an attempt
     * the {@value #CONNECT_TIMEOUT_PROPERTY} property gives no bound of its own, too: turning the
     * per-attempt bound off must not turn the bound of the borrow off with it. Never 0 for an
     * attempt that is bounded at all: 0 is the value that stands for no bound. And never past what
     * an int of milliseconds takes - the pool timeout has no upper bound of its own, while the SQL
     * Server driver rejects a socketTimeout beyond Integer.MAX_VALUE outright, failing every
     * connect of that backend with the name of a property nobody typed.
     */
    static long attemptSeconds(long connectTimeoutSeconds, long deadline) {
        if (deadline == Long.MAX_VALUE) {
            // 0 stands for an attempt with no bound of its own and stays 0; anything else is
            // clamped here as well, so that the range holds whichever branch answers
            return connectTimeoutSeconds == 0 ? 0 : Math.min(connectTimeoutSeconds, Integer.MAX_VALUE / 1000);
        }
        final long remainingSeconds = (deadline - System.currentTimeMillis() + 999) / 1000;
        final long bound = connectTimeoutSeconds == 0
            ? remainingSeconds : Math.min(connectTimeoutSeconds, remainingSeconds);
        return Math.max(1, Math.min(bound, Integer.MAX_VALUE / 1000));
    }
 
    private static boolean isUsable(CachedConnection con, boolean trusted) {
        if (trusted && isKnownAlive(con)) {
            return true;
        }
        // The validation needs a bound of its own: isValid(0) means "no timeout" in the JDBC
        // contract, and a connection whose socket is half-open answers it no sooner than it
        // answers anything else. isValid(n) is not that bound on every driver either - the SQL
        // Server driver turns it into a query timeout (setQueryTimeout, then "SELECT 1"), which
        // needs an answer from the server to fire at all - so the socket is bounded here, for the
        // validation only.
        final int restore = boundValidation(con.parent);
        if (restore == VALIDATION_BOUND_FAILED) {
            // the bound of the validation is not in force, and the driver may well have applied it
            // before failing: validating here would be the unbounded isValid() this exists to
            // avoid, and pooling it would hand out a connection carrying a bound of ours
            return false;
        }
        // Read before the round trip rather than after it: this stamp is what distrustPool() is
        // compared against, as an ordering of two moments. A validation is allowed
        // VALIDATION_TIMEOUT_SECONDS, so a stamp filed once the answer is in can be younger than a
        // drop another operation reported while it was still in flight - and the connection would
        // then be trusted for the rest of the window by the very check that exists to stop it.
        final long provenAt = System.nanoTime();
        boolean usable;
        try {
            usable = con.isValid(VALIDATION_TIMEOUT_SECONDS);
        } catch (SQLException | RuntimeException e) { // a driver reporting the validation as an error: discard it
            // an unchecked failure out of a driver would unwind through poll(), which stands
            // outside every try of the borrow, and leave this connection dequeued and unclosed
            usable = false;
        }
        if (!usable) {
            // On its way out, and the driver knows it: Connector/J answers a failed validation by
            // aborting the connection and the SQL Server driver by terminating it, so putting the
            // previous bound back would fail as well - and warn about a bound of a connection that
            // is about to be closed, over a reaped idle connection that is nobody's problem.
            return false;
        }
        if (restore >= 0 && !setNetworkTimeout(con.parent, restore,
                "the connection is closed rather than pooled")) {
            return false; // it would carry the bound of the validation into every statement
        }
        con.lastKnownAliveNanos = provenAt;
        return true;
    }
 
    /** A connection left alone by {@link #boundValidation}: no bound of ours to put back afterwards. */
    private static final int VALIDATION_BOUND_LEFT_ALONE = -1;
    /** A connection {@link #boundValidation} could not bound, which may still carry the bound it failed to report. */
    private static final int VALIDATION_BOUND_FAILED = -2;
 
    /**
     * Whether a connection can be handed out on the strength of the last answer it gave, without a
     * round trip to ask for another. Three things have to hold: the window is on, the answer is
     * younger than it, and nothing has reported since that the database dropped a connection of
     * this pool.
     * <p>
     * What the window trades away is the connection that breaks inside it: it is handed out, and
     * the failure surfaces on the statement of the caller rather than on the borrow. That is where
     * a connection breaking mid-operation surfaces anyway - but not every caller of this backend
     * reports such a failure to the client, so the trade is not the caller's alone to bear.
     * {@code JDBCStorage} answers it on both sides: a write is replayed on a connection the next
     * attempt borrows of its own, and a read as much as a write marks the pool distrusted, which
     * closes the window for the rest of the generation the dropped connection belonged to.
     */
    private static boolean isKnownAlive(CachedConnection con) {
        final long window = aliveBypassNanos;
        if (window <= 0) {
            return false;
        }
        final long provenAt = con.lastKnownAliveNanos;
        if (System.nanoTime() - provenAt >= window) { // the overflow safe form of the comparison
            return false;
        }
        final Long distrusted = poolDistrustedAt.get(con.connectionString);
        if (distrusted != null && provenAt - distrusted <= 0) { // the overflow safe form of the comparison
            return false;
        }
        // What the validation this replaces also answered, asked of the driver out of a flag of its
        // own rather than by a round trip: a connection the driver has already given up on - the
        // database dropped it and the driver noticed - is not one to hand out on the strength of a
        // window. It no longer stands for a drain closing a connection under its borrower, the way
        // it did while the pool was a cache entry whose removalListener iterated a weakly consistent
        // view of the deque: every path that destroys an idle connection now takes it out of the
        // deque first (pollIdle, drainIdle, and the removeLastOccurrence of the sweep), so what a
        // borrow holds is not there to be found (issue #878).
        return !isClosed(con.parent);
    }
 
    /** Whether the driver reports the connection as closed; one that cannot say is not one to trust. */
    private static boolean isClosed(Connection con) {
        try {
            return con.isClosed();
        } catch (SQLException e) {
            return true;
        }
    }
 
    /**
     * Reports that the database dropped a connection of this pool, so that no connection proven
     * alive before now is handed out unvalidated again. It is called by the operation that saw the
     * failure: this class only ever learns of one from the statement it broke, since a borrow
     * inside the window asks the database nothing.
     */
    static void distrustPool(String connectionString) {
        // merge(later of the two) rather than computeIfAbsent().set(): two operations reporting a
        // drop at once would otherwise move the distrust point backwards - the later reading is
        // written first and the earlier one overwrites it - and the AtomicLong of computeIfAbsent
        // is published holding its initial 0 before set() runs, which a borrow racing it reads as
        // "never". Not Math.max: nanoTime() has no defined origin, so the readings are compared by
        // their difference, the way every other comparison of one in this class is.
        poolDistrustedAt.merge(connectionString, System.nanoTime(),
            (reported, now) -> now - reported > 0 ? now : reported);
    }
 
    /**
     * Bounds the socket of a pooled connection for the length of its validation, returning the
     * network timeout to put back afterwards - or {@link #VALIDATION_BOUND_LEFT_ALONE} for a
     * connection left alone, either because the driver does not take a network timeout or because
     * it is bounded at least as tightly already, by a read timeout of the connection string that
     * is not ours to widen.
     * A driver that takes the call and then fails inside it is told apart from both: it is free to
     * have applied the bound before failing, and a connection put back into the pool carrying five
     * seconds of ours fails every statement slower than that for the rest of its life.
     */
    private static int boundValidation(Connection con) {
        final int bound = VALIDATION_TIMEOUT_SECONDS * 1000;
        final int previous;
        try {
            previous = con.getNetworkTimeout();
        } catch (SQLException | RuntimeException e) { // a driver that does not take one: nothing was changed
            return VALIDATION_BOUND_LEFT_ALONE;
        }
        if (previous > 0 && previous <= bound) {
            return VALIDATION_BOUND_LEFT_ALONE;
        }
        try {
            con.setNetworkTimeout(DIRECT_EXECUTOR, bound);
        } catch (SQLException | RuntimeException e) {
            return VALIDATION_BOUND_FAILED;
        }
        // A driver answering a negative timeout is outside the contract of getNetworkTimeout(),
        // where 0 stands for no limit and nothing below it stands for anything. Handed back as it
        // is, it would be one of the two sentinels above: the bound just set would be read as a
        // bound that was never set, and the connection would go into the pool carrying five
        // seconds of ours into every statement of whoever borrows it next.
        return previous < 0 ? 0 : previous;
    }
 
    static CachedConnection connect(String connectionString, ConnectDialect dialect, long connectTimeoutSeconds,
            Pool pool, boolean metered) throws SQLException {
        // A driver is free to write into the map it is handed, so it gets one of its own.
        final Properties properties = new Properties();
        final boolean readBoundSet = dialect != null && connectTimeoutSeconds > 0
            && dialect.bound(connectionString, properties, connectTimeoutSeconds);
        // Read before the connect rather than after it, for the reason isUsable() reads it before
        // the validation: the login answered somewhere inside this attempt, and a stamp taken once
        // it returned could outlive a drop reported while it was still going on.
        final long provenAt = System.nanoTime();
        final Connection conNew = DriverManager.getConnection(connectionString, properties);
        boolean poolable = true;
        try {
            // still under the read bound: both of these are round trips of their own
            conNew.setAutoCommit(false);
            conNew.setTransactionIsolation(TRANSACTION_READ_COMMITTED);
            if (readBoundSet) {
                // a driver that will not take the bound back has warned about it already: the
                // connection serves the borrower that is waiting for it and is closed rather than
                // pooled, so the bound of the login does not outlive it in the pool
                poolable = relaxReadBound(conNew, connectTimeoutSeconds);
            }
        } catch (SQLException | RuntimeException e) { // nothing holds this connection yet: it would leak
            closeQuietly(conNew);
            throw e;
        }
        final CachedConnection established = new CachedConnection(connectionString, conNew, pool, metered, poolable);
        established.lastKnownAliveNanos = provenAt;
        return established;
    }
 
    // The second bound of the login is a socket read timeout on mysql, oracle and sql server, in
    // force for the whole life of the connection: left in place it would break every statement
    // slower than it - an import batch, the statistics of a freshly loaded table - so it is lifted
    // as soon as the login is through, restoring the behaviour of a connection this class
    // established before. A read bound the connection string sets itself is never touched here:
    // it is not set at all, so nothing of the administrator's is lifted along with it. Returns
    // whether the bound is gone - a connection still carrying it must not be pooled.
    // Named by the bound the login was given rather than by the property it came from: with
    // CONNECT_TIMEOUT_PROPERTY at 0 the attempt takes its bound from what is left of the deadline
    // of the borrow, so naming that property would point at the one setting that is not in force.
    private static boolean relaxReadBound(Connection con, long boundSeconds) {
        return setNetworkTimeout(con, 0, "statements taking longer than the " + boundSeconds
            + "s the login of this connection was bounded by fail on it, and it is closed rather than pooled");
    }
 
    /**
     * Puts a network timeout on a connection, reporting a driver that will not take one. The
     * consequence is the caller's to name: the same failure ends a freshly established connection
     * carrying the read bound of its login and a pooled one whose bound could not be put back.
     */
    private static boolean setNetworkTimeout(Connection con, int millis, String consequence) {
        try {
            con.setNetworkTimeout(DIRECT_EXECUTOR, millis);
            return true;
        } catch (SQLException | RuntimeException e) {
            // Throttled rather than reported once for the life of the JVM: every connection this
            // happens to carries a read bound it was never meant to keep, and a statement dying of
            // it hours later needs a warning of its own to be traced back to here.
            final long now = System.currentTimeMillis();
            final long last = lastReadBoundWarning.get();
            if (now - last >= STALL_WARNING_INTERVAL_MS && lastReadBoundWarning.compareAndSet(last, now)) {
                logger.warn(LocalizableMessage.raw(
                    "The read bound of a JDBC connection could not be set to %d ms (%s): %s",
                    millis, e.getMessage(), consequence));
            }
            return false;
        }
    }
 
    /**
     * Whether the database took no connection for the moment, rather than refusing one for good:
     * it is at its connection limit - one of our own connections is on its way back to the pool -
     * or it is not accepting connections yet, the state a database on its way up reports while it
     * recovers - the one JDBCStorage.open() has no second attempt of its own for, so a backend
     * that meets it stays locked down until the server is restarted. Both clear themselves in
     * seconds; every other failure is the caller's to see.
     */
    static boolean isWorthRetrying(SQLException e, ConnectDialect dialect) {
        // a failure of the driver is often wrapped, and a SQLException carries two chains of its
        // own: the causes behind it and the further exceptions of getNextException()
        final Deque<Throwable> pending = new ArrayDeque<>();
        final Set<Throwable> visited = Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
        enqueue(pending, visited, e);
        for (int links = 0; !pending.isEmpty() && links < MAX_CHAIN_LENGTH; links++) {
            final Throwable t = pending.poll();
            if (t instanceof SQLException) {
                final SQLException sql = (SQLException) t;
                final String sqlState = sql.getSQLState();
                if (CONNECTION_LIMIT_SQL_STATE.equals(sqlState) || NOT_ACCEPTING_YET_SQL_STATE.equals(sqlState)
                    || (dialect != null && dialect.isWorthRetrying(sql.getErrorCode()))) {
                    return true;
                }
                enqueue(pending, visited, sql.getNextException());
            }
            enqueue(pending, visited, t.getCause());
        }
        return false;
    }
 
    // The bound of the pool is a reason for an operation to fail that no version before it had,
    // so it belongs in the server log as well as in the error the client is given. Throttled like
    // the stall warning: every worker thread reaches it at once when the pool stands full.
    private static void warnPoolFull(String connectionString, String message) {
        final long now = System.currentTimeMillis();
        final AtomicLong lastOfThisUrl =
            lastPoolFullWarning.computeIfAbsent(safeUrl(connectionString), url -> new AtomicLong());
        final long last = lastOfThisUrl.get();
        if (now - last >= STALL_WARNING_INTERVAL_MS && lastOfThisUrl.compareAndSet(last, now)) {
            logger.warn(LocalizableMessage.raw("%s", message));
        }
    }
 
    // A stall has to reach the server log: without it a database accepting no further connection
    // 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) {
        final long now = System.currentTimeMillis();
        if (now - startedAt < STALL_WARNING_AFTER_MS) {
            return;
        }
        final AtomicLong lastOfThisUrl =
            lastStallWarning.computeIfAbsent(safeUrl(connectionString), 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)));
        }
    }
 
    /**
     * 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
     * stands - is a rule a test can hold it to.
     */
    static String stallMessage(String connectionString, int attempts, long waitedMs, SQLException cause) {
        return String.format("%s takes no further connection: waiting %d ms for a pooled one so far (%d attempts),"
            + " last error: %s", safeUrl(connectionString), waitedMs, attempts,
            redact(cause.getMessage(), connectionString));
    }
 
    /**
     * The failure of a connect as it may leave this class: the exception itself where nothing of it
     * names the credentials of the backend, and a redacted rebuild of its whole chain where
     * something does. Rebuilt rather than wrapped: a wrapper keeps its cause, and everything that
     * prints a failure prints the causes along with it - a debug build of
     * stackTraceToSingleLineString walks them, the config manager traces them, and
     * RootContainer.open() makes the message of the cause the message of what it throws - so a
     * link left as it stands would carry the password past the wrapper. The SQLState and the
     * vendor code of every link survive it: they are what tells a caller what happened.
     */
    static SQLException reported(SQLException e, String connectionString) {
        return holdsCredentials(e, connectionString)
            ? redactedCopy(e, connectionString, new int[] { MAX_CHAIN_LENGTH })
            : e;
    }
 
    /** The same of an unchecked failure: a driver is free to report a connect it will not make as one. */
    static Exception reportedUnchecked(RuntimeException e, String connectionString) {
        if (!holdsCredentials(e, connectionString)) {
            return e;
        }
        final SQLException redacted = new SQLNonTransientConnectionException(e.getClass().getName()
            + (e.getMessage() == null ? "" : ": " + redact(e.getMessage(), connectionString)),
            CONNECT_FAILED_SQL_STATE);
        redacted.setStackTrace(e.getStackTrace());
        return redacted;
    }
 
    /**
     * Whether anything in the chain of a failure names what a connection string keeps out of the
     * log. A chain longer than this walk is given answers "yes": what is reported unredacted is
     * what this class has looked at whole, and a link it never reached is not that. The cost of
     * being wrong that way is a chain rebuilt - bounded in its turn - while the cost of being
     * wrong the other way is the password of the backend in the server error log.
     */
    private static boolean holdsCredentials(Throwable failure, String connectionString) {
        final Deque<Throwable> pending = new ArrayDeque<>();
        final Set<Throwable> visited = Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
        enqueue(pending, visited, failure);
        for (int links = 0; !pending.isEmpty(); links++) {
            if (links >= MAX_CHAIN_LENGTH) {
                return true;
            }
            final Throwable t = pending.poll();
            final String message = t.getMessage();
            if (message != null && !message.equals(redact(message, connectionString))) {
                return true;
            }
            if (t instanceof SQLException) {
                enqueue(pending, visited, ((SQLException) t).getNextException());
            }
            enqueue(pending, visited, t.getCause());
        }
        return false;
    }
 
    // By identity rather than by equals(): a link of a chain carries a cause and a next exception
    // both, and a driver is free to make the two the same failure. Enqueued twice, a chain of
    // those fans out into a copy of itself at every step and spends the budget of a walk on links
    // it has already looked at - five levels of one are enough to exhaust MAX_CHAIN_LENGTH.
    private static void enqueue(Deque<Throwable> pending, Set<Throwable> visited, Throwable t) {
        if (t != null && visited.add(t)) {
            pending.add(t);
        }
    }
 
    // The budget counts the links this rebuilds, the way holdsCredentials() counts the ones it
    // visits - not how deep it has gone. A link of a chain carries a cause and a next exception
    // both, and a driver is free to make them the same failure, so a bound on depth alone leaves
    // room for a chain that fans out into two copies of itself at every step.
    private static SQLException redactedCopy(SQLException e, String connectionString, int[] budget) {
        budget[0]--;
        final SQLException copy =
            new SQLException(redact(e.getMessage(), connectionString), e.getSQLState(), e.getErrorCode());
        copy.setStackTrace(e.getStackTrace());
        if (e.getNextException() != null) {
            copy.setNextException(budget[0] > 0
                ? redactedCopy(e.getNextException(), connectionString, budget) : droppedTail());
        }
        if (e.getCause() != null) {
            copy.initCause(budget[0] > 0 ? redactedLink(e.getCause(), connectionString, budget) : droppedTail());
        }
        return copy;
    }
 
    // What stands where the budget ran out. Without it the same failure logs its root cause when
    // the url of the backend has no password in it and loses it without a word when it has, which
    // is a report of a connect nobody can read against a report of one they can.
    private static SQLException droppedTail() {
        return new SQLException("the rest of this failure was left out: a chain of more than "
            + MAX_CHAIN_LENGTH + " links is rebuilt only that far");
    }
 
    // A link that is no SQLException keeps its class name in the message: its type is not one this
    // can rebuild, and the name of the failure is what a reader of the log is after.
    private static Throwable redactedLink(Throwable t, String connectionString, int[] budget) {
        if (t instanceof SQLException) {
            return redactedCopy((SQLException) t, connectionString, budget);
        }
        budget[0]--;
        final Throwable copy = new Throwable(t.getClass().getName()
            + (t.getMessage() == null ? "" : ": " + redact(t.getMessage(), connectionString)));
        copy.setStackTrace(t.getStackTrace());
        if (t.getCause() != null) {
            copy.initCause(budget[0] > 0 ? redactedLink(t.getCause(), connectionString, budget) : droppedTail());
        }
        return copy;
    }
 
    /**
     * A message of a driver as it may be logged. A driver is free to put the connection string it
     * was handed into it - the jdk itself does, "No suitable driver found for " + url, which is
     * what the ordinary oracle misconfiguration of a driver jar left out of lib/extensions arrives
     * as - and that connection string is where the credentials of this backend live.
     * What it cannot answer for is a driver quoting back a part of a url it failed to parse:
     * a whole credential is replaced, a fragment of one is not.
     */
    static String redact(String message, String connectionString) {
        if (message == null || message.isEmpty()) {
            return message;
        }
        String redacted = message.replace(connectionString, safeUrl(connectionString));
        // The parameter before the values: a password blanked here is one the loop below no longer
        // finds, while the other way round a "<credentials hidden>" standing behind a "password="
        // would be cut in half by a pattern that ends its value at the first space.
        redacted = SECRET_PARAMETER.matcher(redacted).replaceAll("$1=***");
        for (final String secret : secretsOf(connectionString)) {
            redacted = secretPattern(secret).matcher(redacted)
                .replaceAll(Matcher.quoteReplacement(CREDENTIALS_HIDDEN));
        }
        return redacted;
    }
 
    /**
     * A secret as it is looked for in a message: the value itself, wherever it does not stand
     * inside a longer run of letters and digits. A password is free to be one character long, and
     * a bare one of those is a substring of half the lines a driver writes - a password of "1"
     * takes "ORA-12541: TNS:no listener" apart into a line nobody can read, and it makes every
     * failure of that backend one this class believes names the credentials, so the whole chain is
     * rebuilt as well. A redaction that destroys the diagnostic it protects is the worse of the
     * two failures. What a driver quotes back is a credential standing on its own - between the
     * delimiters of a url, or in a sentence of its own - and that is still replaced.
     */
    private static Pattern secretPattern(String secret) {
        final String before = isAlphanumeric(secret.charAt(0)) ? "(?<![A-Za-z0-9])" : "";
        final String after = isAlphanumeric(secret.charAt(secret.length() - 1)) ? "(?![A-Za-z0-9])" : "";
        return Pattern.compile(before + Pattern.quote(secret) + after);
    }
 
    private static boolean isAlphanumeric(char c) {
        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
    }
 
    /** What of a connection string must not stand in a message: the credentials safeUrl() takes out of it. */
    private static List<String> secretsOf(String connectionString) {
        final List<String> secrets = new ArrayList<>();
        final ConnectDialect dialect = ConnectDialect.of(connectionString);
        final String separators = dialect == null ? "?;" : String.valueOf(dialect.parameterSeparator);
        final int scheme = connectionString.indexOf(':', "jdbc:".length()) + 1;
        if (scheme <= 0) {
            return secrets;
        }
        final int authority = startOfAuthority(connectionString, scheme);
        if (authority < 0) {
            final int at = connectionString.indexOf('@', scheme);
            if (at > scheme) {
                addSecret(secrets, connectionString.substring(credentialsStart(connectionString, scheme, at), at));
            }
        } else {
            final int end = endOfAuthority(connectionString, authority, separators);
            for (final String host : connectionString.substring(authority, end).split(",", -1)) {
                final int at = host.lastIndexOf('@');
                if (at > 0) {
                    addSecret(secrets, host.substring(0, at));
                }
            }
        }
        final Matcher parameter = SECRET_PARAMETER.matcher(connectionString);
        while (parameter.find()) {
            addSecret(secrets, parameter.group(3));
        }
        return secrets;
    }
 
    // The credentials of one host, and the password inside them without the user name in front of
    // it: a driver quoting a url back names either.
    private static void addSecret(List<String> secrets, String credentials) {
        if (credentials.isEmpty()) {
            return;
        }
        secrets.add(credentials);
        final int password = indexOfAny(credentials, ":/", 0);
        if (password >= 0 && password + 1 < credentials.length()) {
            secrets.add(credentials.substring(password + 1));
        }
    }
 
    // The connection string carries the credentials of the backend - JDBCStorage hands the whole
    // db-directory of the configuration to this class, so the url is the only place they live -
    // and it is never logged as it stands. Three shapes hold them and all three are taken off: the
    // "user/password@" in front of an oracle descriptor; the userinfo of an authority, one per
    // host of it, since a url of Connector/J gives every host credentials of its own
    // ("//u:p@h1:3306,u2:p2@h2:3306"); and the parameters behind their first separator,
    // "?user=...&password=..." on postgresql, mysql and oracle, ";password=..." on sql server.
    // What is left is looked over once more: the key-value host syntax of Connector/J puts a
    // password inside the authority itself ("//address=(host=h)(user=u)(password=p)"), where
    // neither of the first two shapes stands, so a "password=" of any case is blanked out wherever
    // it is left standing.
    //
    // A password is free to hold either of the delimiters, so neither of them is looked for in the
    // whole string. The credentials of an oracle url stand between the subprotocol and the first
    // "@", which is the delimiter of its descriptor, so a "?" of one is part of the password
    // rather than the start of the parameters. Everywhere else they stand inside the authority,
    // between "//" and the path behind it, so a "?" of a password is inside them and an "@" of a
    // parameter value ("?user=u@example.com") is not mistaken for the end of them: the host
    // survives in the message either way.
    //
    // And a url none of this took apart is not logged past its subprotocol. An "@" left standing
    // anywhere but where the credentials of an oracle url ended is one this did not recognize - a
    // password holding a "/" inside an authority, a quoted one holding an "@" - and the host of a
    // stall report is worth less than a password in the server log.
    static String safeUrl(String connectionString) {
        final ConnectDialect dialect = ConnectDialect.of(connectionString);
        final String separators = dialect == null ? "?;" : String.valueOf(dialect.parameterSeparator);
        final int scheme = connectionString.indexOf(':', "jdbc:".length()) + 1; // the end of "jdbc:<subprotocol>:"
        if (scheme <= 0) {
            return CREDENTIALS_HIDDEN;
        }
        final String stripped = stripCredentials(connectionString, scheme, separators);
        final int parameters = indexOfAny(stripped, separators, scheme);
        final String url = parameters < 0 ? stripped : stripped.substring(0, parameters);
        final String redacted = SECRET_PARAMETER.matcher(url).replaceAll("$1=***");
        // an "@" standing anywhere but where the credentials of an oracle url ended is one this
        // did not recognize - a password holding a "/" inside an authority, a quoted one holding
        // an "@" - and the host of a stall report is worth less than a password in the server log
        if (redacted.lastIndexOf('@') > endOfRecognizedCredentials(connectionString, scheme)) {
            return redacted.substring(0, scheme) + CREDENTIALS_HIDDEN;
        }
        return parameters < 0 ? redacted
            : redacted + identifyingParameters(stripped.substring(parameters), dialect);
    }
 
    /**
     * The parameters worth keeping in a message: the ones naming the database rather than whoever
     * connects to it. Two backends of one host answer to the same url up to their parameters, and
     * a stall report that cannot tell them apart is a stall report of neither. Everything else is
     * dropped rather than looked at - a name this does not know is a name free to carry a secret.
     */
    private static String identifyingParameters(String parameters, ConnectDialect dialect) {
        final char separator = dialect == null ? ';' : dialect.parameterSeparator;
        final StringBuilder kept = new StringBuilder();
        for (final String parameter : parameters.split("[?&;]")) {
            final int equals = parameter.indexOf('=');
            if (equals > 0
                && IDENTIFYING_PARAMETERS.contains(parameter.substring(0, equals).toLowerCase(Locale.ROOT))) {
                kept.append(kept.length() == 0 || separator != '?' ? separator : '&').append(parameter);
            }
        }
        return kept.toString();
    }
 
    private static String stripCredentials(String url, int scheme, String separators) {
        final int authority = startOfAuthority(url, scheme);
        if (authority < 0) {
            // no authority: the credentials of an oracle url stand between the subprotocol and the
            // first "@", which is the delimiter of the descriptor behind it - a password holding
            // an "@" of its own has to be quoted for the driver itself
            final int at = url.indexOf('@', scheme);
            return at < 0 ? url : url.substring(0, credentialsStart(url, scheme, at)) + url.substring(at);
        }
        final int end = endOfAuthority(url, authority, separators);
        return url.substring(0, authority) + withoutUserinfo(url.substring(authority, end)) + url.substring(end);
    }
 
    /**
     * Where the credentials of a url that names no authority start: behind the token naming the
     * kind of driver, which stands in front of them ("jdbc:oracle:thin:user/pw@...") and is worth
     * keeping - thin against oci is a first question of an oracle connect. The token is the one
     * right behind the subprotocol rather than the last one in front of the "@", since a password
     * is free to hold a ":" of its own.
     */
    private static int credentialsStart(String url, int scheme, int at) {
        final int driverType = url.indexOf(':', scheme);
        return driverType >= 0 && driverType < at ? driverType + 1 : scheme;
    }
 
    /**
     * The last position a stripped url may still carry an "@" at: where the credentials of an
     * oracle url ended, since the "@" is the delimiter of the descriptor behind them and stays.
     * An authority keeps none of its own - every userinfo of it is taken off, delimiter included.
     */
    private static int endOfRecognizedCredentials(String url, int scheme) {
        final int at = url.indexOf('@', scheme);
        return startOfAuthority(url, scheme) < 0 && at > scheme ? credentialsStart(url, scheme, at) : scheme;
    }
 
    /**
     * Where the hosts of a url of this shape start, or -1 for a url that names no authority. The
     * subprotocol is free to name the kind of connection in front of it - "jdbc:mysql:replication://"
     * - so the "//" is looked for rather than expected right behind the subprotocol. An "@" in
     * front of it belongs to an oracle url ("jdbc:oracle:thin:user/pw@//host"), whose credentials
     * stand where an authority has no place for them.
     */
    private static int startOfAuthority(String url, int scheme) {
        final int slashes = url.indexOf("//", scheme);
        if (slashes < 0 || url.lastIndexOf('@', slashes) >= scheme) {
            return -1;
        }
        // ... and so does a "/" in front of them: it is what separates the credentials of an
        // oracle url ("thin:user/pw@..."), so a password holding a "//" of its own would start an
        // authority inside itself. The "@" ending the credentials stands behind that point, the
        // userinfo taken off is a piece of the password rather than the whole of it, and the "@"
        // the guard of safeUrl() looks for is gone with it - leaving the user name and the head of
        // the password in the message of a stall.
        final int slash = url.indexOf('/', scheme);
        return slash >= 0 && slash < slashes ? -1 : slashes + 2;
    }
 
    /**
     * Where the hosts of an authority end: at the path behind them - a password holds a "?" more
     * readily than a "/" - or at the first parameter of a url that has no path.
     */
    private static int endOfAuthority(String url, int authority, String separators) {
        final int path = url.indexOf('/', authority);
        if (path >= 0) {
            return path;
        }
        final int parameter = indexOfAny(url, separators, authority);
        return parameter < 0 ? url.length() : parameter;
    }
 
    /** The hosts of an authority, each of them without the credentials a url may give it. */
    private static String withoutUserinfo(String authority) {
        final StringBuilder hosts = new StringBuilder();
        final String[] split = authority.split(",", -1);
        for (int i = 0; i < split.length; i++) {
            if (i > 0) { // by the position rather than by what is in hand: a first host may be empty
                hosts.append(',');
            }
            final int at = split[i].lastIndexOf('@');
            hosts.append(at < 0 ? split[i] : split[i].substring(at + 1));
        }
        return hosts.toString();
    }
 
    private static int indexOfAny(String url, String separators, int from) {
        int found = -1;
        for (int i = 0; i < separators.length(); i++) {
            final int at = url.indexOf(separators.charAt(i), from);
            if (at >= 0 && (found < 0 || at < found)) {
                found = at;
            }
        }
        return found;
    }
 
    private static void closeQuietly(Connection con) {
        try {
            con.close();
        } catch (SQLException | RuntimeException e) {
            // ignore: it is on its way out anyway, and the caller has a permit to give back
        }
    }
 
    @Override
    public Statement createStatement() throws SQLException {
        return parent.createStatement();
    }
 
    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return parent.prepareStatement(sql);
    }
 
    @Override
    public CallableStatement prepareCall(String sql) throws SQLException {
        return parent.prepareCall(sql);
    }
 
    @Override
    public String nativeSQL(String sql) throws SQLException {
        return parent.nativeSQL(sql);
    }
 
    @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {
        parent.setAutoCommit(autoCommit);
    }
 
    @Override
    public boolean getAutoCommit() throws SQLException {
        return parent.getAutoCommit();
    }
 
    @Override
    public void commit() throws SQLException {
        parent.commit();
    }
 
    @Override
    public void rollback() throws SQLException {
        parent.rollback();
    }
 
    @Override
    public void close() throws SQLException {
        // JDBC makes close() on a closed connection a no-op, and this one has to be one: a second
        // return would put the same connection into the pool twice, to be handed to two borrowers.
        if (!returned.compareAndSet(false, true)) {
            return;
        }
        final AtomicInteger borrowerDepth = depth;
        depth = null;
        if (borrowerDepth != null) {
            Pool.leave(borrowerDepth);
        }
        // Set before the hand-off rather than after it: from the moment give() is called the pool
        // owns this connection, and a second destroy() of one that reached the idle deque would
        // close a connection still waiting there to be handed out.
        boolean handedToPool = false;
        try {
            rollback();
            if (poolable) {
                // Straight to the pool it came from rather than through a lookup of its connection
                // string: the entry the lookup returned could be evicted between the two, leaving
                // the connection in a queue nothing referred to any more - never handed out, never
                // closed (issue #878).
                handedToPool = true;
                pool.give(this);
            }
        } finally {
            // Every way out that is not a give(): the SQLException a rollback is supposed to throw,
            // a connection that may not be pooled, and the unchecked failure a driver throws
            // instead of a SQLException. The CAS above has already made this the one close() of
            // this connection, so what leaves here through neither give() nor destroy() is closed
            // by nothing at all - and its permit is released by nothing either, since destroy() is
            // the only caller of releasePermit(). A pool is never removed from the static map, so
            // that place in the bound would be gone for the life of the server, and enough of them
            // leave every borrow to fail with a SQLTimeoutException (issue #878).
            if (!handedToPool) {
                // destroy() rather than a bare close: the permit this connection holds has to go
                // back to the pool with it, or the bound loses a place for every connection kept
                // out of it.
                pool.destroy(this);
            }
        }
    }
 
    @Override
    public boolean isClosed() throws SQLException {
        return parent.isClosed();
    }
 
    @Override
    public DatabaseMetaData getMetaData() throws SQLException {
        return parent.getMetaData();
    }
 
    @Override
    public void setReadOnly(boolean readOnly) throws SQLException {
        parent.setReadOnly(readOnly);
    }
 
    @Override
    public boolean isReadOnly() throws SQLException {
        return parent.isReadOnly();
    }
 
    @Override
    public void setCatalog(String catalog) throws SQLException {
        parent.setCatalog(catalog);
    }
 
    @Override
    public String getCatalog() throws SQLException {
        return parent.getCatalog();
    }
 
    @Override
    public void setTransactionIsolation(int level) throws SQLException {
        parent.setTransactionIsolation(level);
    }
 
    @Override
    public int getTransactionIsolation() throws SQLException {
        return parent.getTransactionIsolation();
    }
 
    @Override
    public SQLWarning getWarnings() throws SQLException {
        return parent.getWarnings();
    }
 
    @Override
    public void clearWarnings() throws SQLException {
        parent.clearWarnings();
    }
 
    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
        return parent.createStatement(resultSetType, resultSetConcurrency);
    }
 
    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return parent.prepareStatement(sql, resultSetType, resultSetConcurrency);
    }
 
    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return parent.prepareCall(sql, resultSetType, resultSetConcurrency);
    }
 
    @Override
    public Map<String, Class<?>> getTypeMap() throws SQLException {
        return parent.getTypeMap();
    }
 
    @Override
    public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
        parent.setTypeMap(map);
    }
 
    @Override
    public void setHoldability(int holdability) throws SQLException {
        parent.setHoldability(holdability);
    }
 
    @Override
    public int getHoldability() throws SQLException {
        return parent.getHoldability();
    }
 
    @Override
    public Savepoint setSavepoint() throws SQLException {
        return parent.setSavepoint();
    }
 
    @Override
    public Savepoint setSavepoint(String name) throws SQLException {
        return parent.setSavepoint(name);
    }
 
    @Override
    public void rollback(Savepoint savepoint) throws SQLException {
        parent.rollback(savepoint);
    }
 
    @Override
    public void releaseSavepoint(Savepoint savepoint) throws SQLException {
        parent.releaseSavepoint(savepoint);
    }
 
    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return parent.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
    }
 
    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return parent.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
    }
 
    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return parent.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
    }
 
    @Override
    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
        return parent.prepareStatement(sql, autoGeneratedKeys);
    }
 
    @Override
    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
        return parent.prepareStatement(sql, columnIndexes);
    }
 
    @Override
    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
        return parent.prepareStatement(sql, columnNames);
    }
 
    @Override
    public Clob createClob() throws SQLException {
        return parent.createClob();
    }
 
    @Override
    public Blob createBlob() throws SQLException {
        return parent.createBlob();
    }
 
    @Override
    public NClob createNClob() throws SQLException {
        return parent.createNClob();
    }
 
    @Override
    public SQLXML createSQLXML() throws SQLException {
        return parent.createSQLXML();
    }
 
    @Override
    public boolean isValid(int timeout) throws SQLException {
        return parent.isValid(timeout);
    }
 
    @Override
    public void setClientInfo(String name, String value) throws SQLClientInfoException {
        parent.setClientInfo(name, value);
    }
 
    @Override
    public void setClientInfo(Properties properties) throws SQLClientInfoException {
        parent.setClientInfo(properties);
    }
 
    @Override
    public String getClientInfo(String name) throws SQLException {
        return parent.getClientInfo(name);
    }
 
    @Override
    public Properties getClientInfo() throws SQLException {
        return parent.getClientInfo();
    }
 
    @Override
    public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
        return parent.createArrayOf(typeName, elements);
    }
 
    @Override
    public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
        return parent.createStruct(typeName, attributes);
    }
 
    @Override
    public void setSchema(String schema) throws SQLException {
        parent.setSchema(schema);
    }
 
    @Override
    public String getSchema() throws SQLException {
        return parent.getSchema();
    }
 
    @Override
    public void abort(Executor executor) throws SQLException {
        parent.abort(executor);
    }
 
    @Override
    public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
        parent.setNetworkTimeout(executor, milliseconds);
    }
 
    @Override
    public int getNetworkTimeout() throws SQLException {
        return parent.getNetworkTimeout();
    }
 
    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return parent.unwrap(iface);
    }
 
    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return parent.isWrapperFor(iface);
    }
}