@@ -1099,6 +1099,7 @@ private static enum Position {
|
1099 | 1099 | private final ScheduledExecutorService executor;
|
1100 | 1100 | private final ExecutorFactory<ScheduledExecutorService> executorFactory;
|
1101 | 1101 | private final ScheduledExecutorService prepareExecutor;
|
| 1102 | +private final int prepareThreadPoolSize; |
1102 | 1103 | final PoolMaintainer poolMaintainer;
|
1103 | 1104 | private final Clock clock;
|
1104 | 1105 | private final Object lock = new Object();
|
@@ -1143,6 +1144,12 @@ private static enum Position {
|
1143 | 1144 | @GuardedBy("lock")
|
1144 | 1145 | private long numSessionsReleased = 0;
|
1145 | 1146 |
|
| 1147 | +@GuardedBy("lock") |
| 1148 | +private long numSessionsInProcessPrepared = 0; |
| 1149 | + |
| 1150 | +@GuardedBy("lock") |
| 1151 | +private long numSessionsAsyncPrepared = 0; |
| 1152 | + |
1146 | 1153 | @GuardedBy("lock")
|
1147 | 1154 | private long numIdleSessionsRemoved = 0;
|
1148 | 1155 |
|
@@ -1224,15 +1231,14 @@ private SessionPool(
|
1224 | 1231 | this.options = options;
|
1225 | 1232 | this.executorFactory = executorFactory;
|
1226 | 1233 | this.executor = executor;
|
1227 |
| -int prepareThreads; |
1228 | 1234 | if (executor instanceof ThreadPoolExecutor) {
|
1229 |
| -prepareThreads = Math.max(((ThreadPoolExecutor) executor).getCorePoolSize(), 1); |
| 1235 | +prepareThreadPoolSize = Math.max(((ThreadPoolExecutor) executor).getCorePoolSize(), 1); |
1230 | 1236 | } else {
|
1231 |
| -prepareThreads = 8; |
| 1237 | +prepareThreadPoolSize = 8; |
1232 | 1238 | }
|
1233 | 1239 | this.prepareExecutor =
|
1234 | 1240 | Executors.newScheduledThreadPool(
|
1235 |
| -prepareThreads, |
| 1241 | +prepareThreadPoolSize, |
1236 | 1242 | new ThreadFactoryBuilder()
|
1237 | 1243 | .setDaemon(true)
|
1238 | 1244 | .setNameFormat("session-pool-prepare-%d")
|
@@ -1244,6 +1250,19 @@ private SessionPool(
|
1244 | 1250 | }
|
1245 | 1251 |
|
1246 | 1252 | @VisibleForTesting
|
| 1253 | +long getNumberOfSessionsInProcessPrepared() { |
| 1254 | +synchronized (lock) { |
| 1255 | +return numSessionsInProcessPrepared; |
| 1256 | +} |
| 1257 | +} |
| 1258 | + |
| 1259 | +@VisibleForTesting |
| 1260 | +long getNumberOfSessionsAsyncPrepared() { |
| 1261 | +synchronized (lock) { |
| 1262 | +return numSessionsAsyncPrepared; |
| 1263 | +} |
| 1264 | +} |
| 1265 | + |
1247 | 1266 | void removeFromPool(PooledSession session) {
|
1248 | 1267 | synchronized (lock) {
|
1249 | 1268 | if (isClosed()) {
|
@@ -1453,46 +1472,98 @@ PooledSession getReadSession() throws SpannerException {
|
1453 | 1472 | PooledSession getReadWriteSession() {
|
1454 | 1473 | Span span = Tracing.getTracer().getCurrentSpan();
|
1455 | 1474 | span.addAnnotation("Acquiring read write session");
|
1456 |
| -Waiter waiter = null; |
1457 | 1475 | PooledSession sess = null;
|
1458 |
| -synchronized (lock) { |
1459 |
| -if (closureFuture != null) { |
1460 |
| -span.addAnnotation("Pool has been closed"); |
1461 |
| -throw new IllegalStateException("Pool has been closed"); |
| 1476 | +// Loop to retry SessionNotFoundExceptions that might occur during in-process prepare of a |
| 1477 | +// session. |
| 1478 | +while (true) { |
| 1479 | +Waiter waiter = null; |
| 1480 | +boolean inProcessPrepare = false; |
| 1481 | +synchronized (lock) { |
| 1482 | +if (closureFuture != null) { |
| 1483 | +span.addAnnotation("Pool has been closed"); |
| 1484 | +throw new IllegalStateException("Pool has been closed"); |
| 1485 | +} |
| 1486 | +if (resourceNotFoundException != null) { |
| 1487 | +span.addAnnotation("Database has been deleted"); |
| 1488 | +throw SpannerExceptionFactory.newSpannerException( |
| 1489 | +ErrorCode.NOT_FOUND, |
| 1490 | +String.format( |
| 1491 | +"The session pool has been invalidated because a previous RPC returned 'Database not found': %s", |
| 1492 | +resourceNotFoundException.getMessage()), |
| 1493 | +resourceNotFoundException); |
| 1494 | +} |
| 1495 | +sess = writePreparedSessions.poll(); |
| 1496 | +if (sess == null) { |
| 1497 | +if (numSessionsBeingPrepared <= prepareThreadPoolSize) { |
| 1498 | +if (numSessionsBeingPrepared <= readWriteWaiters.size()) { |
| 1499 | +PooledSession readSession = readSessions.poll(); |
| 1500 | +if (readSession != null) { |
| 1501 | +span.addAnnotation( |
| 1502 | +"Acquired read only session. Preparing for read write transaction"); |
| 1503 | +prepareSession(readSession); |
| 1504 | +} else { |
| 1505 | +span.addAnnotation("No session available"); |
| 1506 | +maybeCreateSession(); |
| 1507 | +} |
| 1508 | +} |
| 1509 | +} else { |
| 1510 | +inProcessPrepare = true; |
| 1511 | +numSessionsInProcessPrepared++; |
| 1512 | +PooledSession readSession = readSessions.poll(); |
| 1513 | +if (readSession != null) { |
| 1514 | +// Create a read/write transaction in-process if there is already a queue for prepared |
| 1515 | +// sessions. This is more efficient than doing it asynchronously, as it scales with |
| 1516 | +// the number of user threads. The thread pool for asynchronously preparing sessions |
| 1517 | +// is fixed. |
| 1518 | +span.addAnnotation( |
| 1519 | +"Acquired read only session. Preparing in-process for read write transaction"); |
| 1520 | +sess = readSession; |
| 1521 | +} else { |
| 1522 | +span.addAnnotation("No session available"); |
| 1523 | +maybeCreateSession(); |
| 1524 | +} |
| 1525 | +} |
| 1526 | +if (sess == null) { |
| 1527 | +waiter = new Waiter(); |
| 1528 | +if (inProcessPrepare) { |
| 1529 | +// inProcessPrepare=true means that we have already determined that the queue for |
| 1530 | +// preparing read/write sessions is larger than the number of threads in the prepare |
| 1531 | +// thread pool, and that it's more efficient to do the prepare in-process. We will |
| 1532 | +// therefore create a waiter for a read-only session, even though a read/write session |
| 1533 | +// has been requested. |
| 1534 | +readWaiters.add(waiter); |
| 1535 | +} else { |
| 1536 | +readWriteWaiters.add(waiter); |
| 1537 | +} |
| 1538 | +} |
| 1539 | +} else { |
| 1540 | +span.addAnnotation("Acquired read write session"); |
| 1541 | +} |
1462 | 1542 | }
|
1463 |
| -if (resourceNotFoundException != null) { |
1464 |
| -span.addAnnotation("Database has been deleted"); |
1465 |
| -throw SpannerExceptionFactory.newSpannerException( |
1466 |
| -ErrorCode.NOT_FOUND, |
1467 |
| -String.format( |
1468 |
| -"The session pool has been invalidated because a previous RPC returned 'Database not found': %s", |
1469 |
| -resourceNotFoundException.getMessage()), |
1470 |
| -resourceNotFoundException); |
| 1543 | +if (waiter != null) { |
| 1544 | +logger.log( |
| 1545 | +Level.FINE, |
| 1546 | +"No session available in the pool. Blocking for one to become available/created"); |
| 1547 | +span.addAnnotation("Waiting for read write session to be available"); |
| 1548 | +sess = waiter.take(); |
1471 | 1549 | }
|
1472 |
| -sess = writePreparedSessions.poll(); |
1473 |
| -if (sess == null) { |
1474 |
| -if (numSessionsBeingPrepared <= readWriteWaiters.size()) { |
1475 |
| -PooledSession readSession = readSessions.poll(); |
1476 |
| -if (readSession != null) { |
1477 |
| -span.addAnnotation("Acquired read only session. Preparing for read write transaction"); |
1478 |
| -prepareSession(readSession); |
1479 |
| -} else { |
1480 |
| -span.addAnnotation("No session available"); |
1481 |
| -maybeCreateSession(); |
| 1550 | +if (inProcessPrepare) { |
| 1551 | +try { |
| 1552 | +sess.prepareReadWriteTransaction(); |
| 1553 | +} catch (Throwable t) { |
| 1554 | +sess = null; |
| 1555 | +SpannerException e = newSpannerException(t); |
| 1556 | +if (!isClosed()) { |
| 1557 | +handlePrepareSessionFailure(e, sess, false); |
| 1558 | +} |
| 1559 | +if (!isSessionNotFound(e)) { |
| 1560 | +throw e; |
1482 | 1561 | }
|
1483 | 1562 | }
|
1484 |
| -waiter = new Waiter(); |
1485 |
| -readWriteWaiters.add(waiter); |
1486 |
| -} else { |
1487 |
| -span.addAnnotation("Acquired read write session"); |
1488 | 1563 | }
|
1489 |
| -} |
1490 |
| -if (waiter != null) { |
1491 |
| -logger.log( |
1492 |
| -Level.FINE, |
1493 |
| -"No session available in the pool. Blocking for one to become available/created"); |
1494 |
| -span.addAnnotation("Waiting for read write session to be available"); |
1495 |
| -sess = waiter.take(); |
| 1564 | +if (sess != null) { |
| 1565 | +break; |
| 1566 | +} |
1496 | 1567 | }
|
1497 | 1568 | sess.markBusy();
|
1498 | 1569 | incrementNumSessionsInUse();
|
@@ -1620,7 +1691,8 @@ private void handleCreateSessionsFailure(SpannerException e, int count) {
|
1620 | 1691 | }
|
1621 | 1692 | }
|
1622 | 1693 |
|
1623 |
| -private void handlePrepareSessionFailure(SpannerException e, PooledSession session) { |
| 1694 | +private void handlePrepareSessionFailure( |
| 1695 | +SpannerException e, PooledSession session, boolean informFirstWaiter) { |
1624 | 1696 | synchronized (lock) {
|
1625 | 1697 | if (isSessionNotFound(e)) {
|
1626 | 1698 | invalidateSession(session);
|
@@ -1643,7 +1715,7 @@ private void handlePrepareSessionFailure(SpannerException e, PooledSession sessi
|
1643 | 1715 | MoreObjects.firstNonNull(
|
1644 | 1716 | this.resourceNotFoundException,
|
1645 | 1717 | isDatabaseOrInstanceNotFound(e) ? (ResourceNotFoundException) e : null);
|
1646 |
| -} else if (readWriteWaiters.size() > 0) { |
| 1718 | +} else if (informFirstWaiter && readWriteWaiters.size() > 0) { |
1647 | 1719 | releaseSession(session, Position.FIRST);
|
1648 | 1720 | readWriteWaiters.poll().put(e);
|
1649 | 1721 | } else {
|
@@ -1792,6 +1864,7 @@ public void run() {
|
1792 | 1864 | sess.prepareReadWriteTransaction();
|
1793 | 1865 | logger.log(Level.FINE, "Session prepared");
|
1794 | 1866 | synchronized (lock) {
|
| 1867 | +numSessionsAsyncPrepared++; |
1795 | 1868 | numSessionsBeingPrepared--;
|
1796 | 1869 | if (!isClosed()) {
|
1797 | 1870 | if (readWriteWaiters.size() > 0) {
|
@@ -1807,7 +1880,7 @@ public void run() {
|
1807 | 1880 | synchronized (lock) {
|
1808 | 1881 | numSessionsBeingPrepared--;
|
1809 | 1882 | if (!isClosed()) {
|
1810 |
| -handlePrepareSessionFailure(newSpannerException(t), sess); |
| 1883 | +handlePrepareSessionFailure(newSpannerException(t), sess, true); |
1811 | 1884 | }
|
1812 | 1885 | }
|
1813 | 1886 | }
|
|
0 commit comments