private void closeSession(PooledSession sess) { try { sess.delegate.close(); } catch (SpannerException e) { // Backend will delete these sessions after a while even if we fail to close them. if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "Failed to close session: " + sess.getName(), e); } } finally { synchronized (lock) { allSessions.remove(sess); if (isClosed()) { decrementPendingClosures(); return; } // Create a new session if needed to unblock some waiter. if (numWaiters() > numSessionsBeingCreated) { createSession(); } } } }
@Test public void poolExhaustion() throws Exception { Session session1 = pool.getReadSession(); Session session2 = pool.getReadSession(); final CountDownLatch latch = new CountDownLatch(1); new Thread( new Runnable() { @Override public void run() { try (Session session3 = pool.getReadSession()) { latch.countDown(); } } }) .start(); assertThat(latch.await(5, TimeUnit.SECONDS)).isFalse(); session1.close(); session2.close(); latch.await(); }
@Test public void poolClosureClosesLeakedSessions() throws Exception { Session mockSession1 = mockSession(); Session mockSession2 = mockSession(); when(client.createSession(db)).thenReturn(mockSession1).thenReturn(mockSession2); pool = createPool(); Session session1 = pool.getReadSession(); // Leaked sessions pool.getReadSession(); session1.close(); pool.closeAsync().get(); verify(mockSession1).close(); verify(mockSession2).close(); }
@Test public void multipleWaiters() throws Exception { Session session1 = pool.getReadSession(); Session session2 = pool.getReadSession(); int numSessions = 5; final CountDownLatch latch = new CountDownLatch(numSessions); for (int i = 0; i < numSessions; i++) { new Thread( new Runnable() { @Override public void run() { try (Session session = pool.getReadSession()) { latch.countDown(); } } }) .start(); } session1.close(); session2.close(); // Everyone should get session pretty quickly. assertThat(latch.await(1, TimeUnit.SECONDS)).isTrue(); }
@Test public void closeAfterInitialCreateDoesNotBlockIndefinitely() throws Exception { pool.getReadSession().close(); pool.closeAsync().get(); }
@Test public void getMultipleReadWriteSessions() { Session mockSession1 = mockSession(); Session mockSession2 = mockSession(); when(client.createSession(db)).thenReturn(mockSession1).thenReturn(mockSession2); pool = createPool(); Session session1 = pool.getReadWriteSession(); Session session2 = pool.getReadWriteSession(); verify(mockSession1).prepareReadWriteTransaction(); verify(mockSession2).prepareReadWriteTransaction(); session1.close(); session2.close(); }
@Override public void run() { Uninterruptibles.awaitUninterruptibly(releaseThreads); for (int j = 0; j < numOperationsPerThread; j++) { try { Session session = null; if (random.nextInt(10) < writeOperationFraction) { session = pool.getReadWriteSession(); assertWritePrepared(session); } else { session = pool.getReadSession(); } Uninterruptibles.sleepUninterruptibly( random.nextInt(5), TimeUnit.MILLISECONDS); resetTransaction(session); session.close(); } catch (SpannerException e) { if (e.getErrorCode() != ErrorCode.RESOURCE_EXHAUSTED || shouldBlock) { setFailed(e); } } catch (Exception e) { setFailed(e); } } threadsDone.countDown(); } })
@Test public void createAndCloseSession() { Map<String, String> labels = new HashMap<>(); labels.put("env", "dev"); Mockito.when(spannerOptions.getSessionLabels()).thenReturn(labels); String dbName = "projects/p1/instances/i1/databases/d1"; String sessionName = dbName + "/sessions/s1"; DatabaseId db = DatabaseId.of(dbName); com.google.spanner.v1.Session sessionProto = com.google.spanner.v1.Session.newBuilder() .setName(sessionName) .putAllLabels(labels) .build(); Mockito.when(rpc.createSession(Mockito.eq(dbName), Mockito.eq(labels), options.capture())) .thenReturn(sessionProto); Session session = impl.createSession(db); assertThat(session.getName()).isEqualTo(sessionName); session.close(); // The same channelHint is passed for deleteSession (contained in "options"). Mockito.verify(rpc).deleteSession(sessionName, options.getValue()); }
@Test public void sessionCreation() { try (Session session = pool.getReadSession()) { assertThat(session).isNotNull(); } try (Session session = pool.getReadSession()) { assertThat(session).isNotNull(); Session session2 = pool.getReadSession(); assertThat(session2).isNotNull(); session2.close(); } }
.close(); pool = createPool(clock); pool.getReadSession().close(); runMaintainanceLoop(clock, pool, pool.poolMaintainer.numClosureCycles); assertThat(numSessionClosed.get()).isEqualTo(0); Session readSession2 = pool.getReadSession(); Session readSession3 = pool.getReadSession(); readSession1.close(); readSession2.close(); readSession3.close(); pool.getReadSession().close(); pool.getReadSession().close(); pool.getReadSession().close(); runMaintainanceLoop(clock, pool, pool.poolMaintainer.numClosureCycles);
@Test public void keepAlive() throws Exception { options = SessionPoolOptions.newBuilder().setMinSessions(2).setMaxSessions(3).build(); Session session = mockSession(); mockKeepAlive(session); // This is cheating as we are returning the same session each but it makes the verification // easier. when(client.createSession(db)).thenReturn(session); FakeClock clock = new FakeClock(); clock.currentTimeMillis = System.currentTimeMillis(); pool = createPool(clock); Session session1 = pool.getReadSession(); Session session2 = pool.getReadSession(); session1.close(); session2.close(); runMaintainanceLoop(clock, pool, pool.poolMaintainer.numKeepAliveCycles); verify(session, never()).singleUse(any(TimestampBound.class)); runMaintainanceLoop(clock, pool, pool.poolMaintainer.numKeepAliveCycles); verify(session, times(2)).singleUse(any(TimestampBound.class)); clock.currentTimeMillis += clock.currentTimeMillis + 35 * 60 * 1000; session1 = pool.getReadSession(); session1.writeAtLeastOnce(new ArrayList<Mutation>()); session1.close(); runMaintainanceLoop(clock, pool, pool.poolMaintainer.numKeepAliveCycles); verify(session, times(3)).singleUse(any(TimestampBound.class)); pool.closeAsync().get(); }
@Test public void getReadSessionFallsBackToWritePreparedSession() throws Exception { Session mockSession1 = mockSession(); final CountDownLatch prepareLatch = new CountDownLatch(2); doAnswer( new Answer<Void>() { @Override public Void answer(InvocationOnMock arg0) throws Throwable { prepareLatch.countDown(); return null; } }) .when(mockSession1) .prepareReadWriteTransaction(); when(client.createSession(db)).thenReturn(mockSession1); options = SessionPoolOptions.newBuilder() .setMinSessions(minSessions) .setMaxSessions(1) .setWriteSessionsFraction(1.0f) .build(); pool = createPool(); pool.getReadWriteSession().close(); prepareLatch.await(); // This session should also be write prepared. PooledSession readSession = (PooledSession) pool.getReadSession(); verify(readSession.delegate, times(2)).prepareReadWriteTransaction(); }
@Test public void failOnPoolExhaustion() { options = SessionPoolOptions.newBuilder() .setMinSessions(1) .setMaxSessions(1) .setFailIfPoolExhausted() .build(); Session mockSession = mockSession(); when(client.createSession(db)).thenReturn(mockSession); pool = createPool(); Session session1 = pool.getReadSession(); expectedException.expect(isSpannerException(ErrorCode.RESOURCE_EXHAUSTED)); pool.getReadSession(); session1.close(); session1 = pool.getReadSession(); assertThat(session1).isNotNull(); session1.close(); }
private void closeSession(PooledSession sess) { try { sess.delegate.close(); } catch (SpannerException e) { // Backend will delete these sessions after a while even if we fail to close them. if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "Failed to close session: " + sess.getName(), e); } } finally { synchronized (lock) { allSessions.remove(sess); if (isClosed()) { decrementPendingClosures(); return; } // Create a new session if needed to unblock some waiter. if (numWaiters() > numSessionsBeingCreated) { createSession(); } } } }