Repository navigation
Fix SessionPool's bookkeeping of sessions in use #19092
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,6 +56,7 @@ | |
| import java.util.Collections; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
| import java.util.concurrent.ConcurrentLinkedDeque; | ||
| import java.util.concurrent.ConcurrentMap; | ||
|
|
@@ -100,8 +101,14 @@ public class SessionPool implements ISessionPool { | |
| private static final int FINAL_RETRY = RETRY - 1; | ||
|
|
||
| private final ConcurrentLinkedDeque<ISession> queue = new ConcurrentLinkedDeque<>(); | ||
| // for session whose resultSet is not released. | ||
| private final ConcurrentMap<ISession, ISession> occupied = new ConcurrentHashMap<>(); | ||
| // Every session taken by getSession() and not yet returned (put back or cleaned), whatever it | ||
| // is used for. | ||
| private final Set<ISession> occupied = ConcurrentHashMap.newKeySet(); | ||
| // The occupied sessions whose result set is not released yet, each with that result set. A | ||
| // session can be released, taken again and hold a new result set while the old one is still | ||
| // referenced, so a release must match the result set, not only the session. | ||
| private final ConcurrentMap<ISession, SessionDataSetWrapper> occupiedByResultSet = | ||
| new ConcurrentHashMap<>(); | ||
| private int size = 0; | ||
| private int maxSize = 0; | ||
| private final long waitToGetSessionTimeoutInMs; | ||
|
|
@@ -702,6 +709,7 @@ private ISession getSession() throws IoTDBConnectionException { | |
| throw new IoTDBConnectionException(SESSION_POOL_IS_CLOSED); | ||
| } | ||
| if (session != null) { | ||
| occupied.add(session); | ||
| return session; | ||
| } | ||
|
|
||
|
|
@@ -790,6 +798,7 @@ private ISession getSession() throws IoTDBConnectionException { | |
| } | ||
| } | ||
|
|
||
| occupied.add(session); | ||
| return session; | ||
| } | ||
|
|
||
|
|
@@ -802,13 +811,18 @@ public int currentAvailableSize() { | |
| return queue.size(); | ||
| } | ||
|
|
||
| /** | ||
| * The number of sessions currently taken from the pool: those executing a request (insert, | ||
| * non-query, query, ...) and those held by a query result set that has not been closed yet. | ||
| */ | ||
| @Override | ||
| public int currentOccupiedSize() { | ||
| return occupied.size(); | ||
| } | ||
|
|
||
| @SuppressWarnings({"squid:S2446"}) | ||
| protected void putBack(ISession session) { | ||
| occupied.remove(session); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
These are the same symptoms as bugs (2) and (3), just in a narrower window. Moving the remove/push (and a |
||
| queue.push(session); | ||
| synchronized (this) { | ||
| // we do not need to notifyAll as any waited thread can continue to work after waked up. | ||
|
|
@@ -821,8 +835,8 @@ protected void putBack(ISession session) { | |
| } | ||
| } | ||
|
|
||
| private void occupy(ISession session) { | ||
| occupied.put(session, session); | ||
| private void occupy(ISession session, SessionDataSetWrapper wrapper) { | ||
| occupiedByResultSet.put(session, wrapper); | ||
| } | ||
|
|
||
| /** close all connections in the pool */ | ||
|
|
@@ -836,7 +850,8 @@ public synchronized void close() { | |
| LOGGER.warn(CLOSE_THE_SESSION_FAILED, e); | ||
| } | ||
| } | ||
| for (ISession session : occupied.keySet()) { | ||
| // sessions executing a request are closed as well; that request then fails | ||
| for (ISession session : occupied) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Closing sessions that are still in use from another thread has side effects beyond making the running request fail:
Suggestion: instead of closing sessions while they are in use, close them when they come back to a closed pool:
In-flight requests then finish normally, their connections are closed when they are returned, and |
||
| try { | ||
| session.close(); | ||
| } catch (IoTDBConnectionException e) { | ||
|
|
@@ -855,23 +870,26 @@ public synchronized void close() { | |
| LOGGER.info(SessionMessages.CLOSING_SESSION_POOL); | ||
| this.closed = true; | ||
| queue.clear(); | ||
| occupiedByResultSet.clear(); | ||
| occupied.clear(); | ||
| } | ||
|
|
||
| @Override | ||
| public void closeResultSet(SessionDataSetWrapper wrapper) { | ||
| boolean putback = true; | ||
| ISession session = wrapper.getSession(); | ||
| // SessionDataSetWrapper.hasNext() already releases an exhausted result set, so this is often | ||
| // called twice for the same one. Only the first call may hand its session back; a later one | ||
| // must not release the session again, which may already hold another result set by then. | ||
| if (session == null || !occupiedByResultSet.remove(session, wrapper)) { | ||
| return; | ||
| } | ||
| try { | ||
| wrapper.getSessionDataSet().closeOperationHandle(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor: the old |
||
| } catch (IoTDBConnectionException | StatementExecutionException e) { | ||
| tryConstructNewSession(); | ||
| putback = false; | ||
| } finally { | ||
| ISession session = occupied.remove(wrapper.getSession()); | ||
| if (putback && session != null) { | ||
| putBack(wrapper.getSession()); | ||
| } | ||
| cleanSessionAndMayThrowConnectionException(session); | ||
| return; | ||
| } | ||
| putBack(session); | ||
| } | ||
|
|
||
| @SuppressWarnings({"squid:S2446"}) | ||
|
|
@@ -905,6 +923,7 @@ private void tryConstructNewSession() { | |
|
|
||
| private void closeSession(ISession session) { | ||
| if (session != null) { | ||
| occupied.remove(session); | ||
| try { | ||
| session.close(); | ||
| } catch (Exception e2) { | ||
|
|
@@ -3077,7 +3096,7 @@ public SessionDataSetWrapper executeQueryStatement(String sql) | |
| try { | ||
| SessionDataSet resp = session.executeQueryStatement(sql); | ||
| SessionDataSetWrapper wrapper = new SessionDataSetWrapper(resp, session, this); | ||
| occupy(session); | ||
| occupy(session, wrapper); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: wrapper construction followed by |
||
| return wrapper; | ||
| } catch (IoTDBConnectionException e) { | ||
| // TException means the connection is broken, remove it and get a new one. | ||
|
|
@@ -3115,7 +3134,7 @@ public SessionDataSetWrapper executeQueryStatement(String sql, long timeoutInMs) | |
| try { | ||
| SessionDataSet resp = session.executeQueryStatement(sql, timeoutInMs); | ||
| SessionDataSetWrapper wrapper = new SessionDataSetWrapper(resp, session, this); | ||
| occupy(session); | ||
| occupy(session, wrapper); | ||
| return wrapper; | ||
| } catch (IoTDBConnectionException e) { | ||
| // TException means the connection is broken, remove it and get a new one. | ||
|
|
@@ -3179,7 +3198,7 @@ public SessionDataSetWrapper executeRawDataQuery( | |
| try { | ||
| SessionDataSet resp = session.executeRawDataQuery(paths, startTime, endTime, timeOut); | ||
| SessionDataSetWrapper wrapper = new SessionDataSetWrapper(resp, session, this); | ||
| occupy(session); | ||
| occupy(session, wrapper); | ||
| return wrapper; | ||
| } catch (IoTDBConnectionException e) { | ||
| // TException means the connection is broken, remove it and get a new one. | ||
|
|
@@ -3206,7 +3225,7 @@ public SessionDataSetWrapper executeLastDataQuery(List<String> paths, long lastT | |
| try { | ||
| SessionDataSet resp = session.executeLastDataQuery(paths, lastTime); | ||
| SessionDataSetWrapper wrapper = new SessionDataSetWrapper(resp, session, this); | ||
| occupy(session); | ||
| occupy(session, wrapper); | ||
| return wrapper; | ||
| } catch (IoTDBConnectionException e) { | ||
| // TException means the connection is broken, remove it and get a new one. | ||
|
|
@@ -3233,7 +3252,7 @@ public SessionDataSetWrapper executeLastDataQuery(List<String> paths, long lastT | |
| try { | ||
| SessionDataSet resp = session.executeLastDataQuery(paths, lastTime, timeOut); | ||
| SessionDataSetWrapper wrapper = new SessionDataSetWrapper(resp, session, this); | ||
| occupy(session); | ||
| occupy(session, wrapper); | ||
| return wrapper; | ||
| } catch (IoTDBConnectionException e) { | ||
| // TException means the connection is broken, remove it and get a new one. | ||
|
|
@@ -3260,7 +3279,7 @@ public SessionDataSetWrapper executeLastDataQuery(List<String> paths) | |
| try { | ||
| SessionDataSet resp = session.executeLastDataQuery(paths); | ||
| SessionDataSetWrapper wrapper = new SessionDataSetWrapper(resp, session, this); | ||
| occupy(session); | ||
| occupy(session, wrapper); | ||
| return wrapper; | ||
| } catch (IoTDBConnectionException e) { | ||
| // TException means the connection is broken, remove it and get a new one. | ||
|
|
@@ -3287,7 +3306,7 @@ public SessionDataSetWrapper executeFastLastDataQueryForOnePrefixPath(final List | |
| try { | ||
| SessionDataSet resp = session.executeFastLastDataQueryForOnePrefixPath(prefixes); | ||
| SessionDataSetWrapper wrapper = new SessionDataSetWrapper(resp, session, this); | ||
| occupy(session); | ||
| occupy(session, wrapper); | ||
| return wrapper; | ||
| } catch (IoTDBConnectionException e) { | ||
| // TException means the connection is broken, remove it and get a new one. | ||
|
|
@@ -3316,7 +3335,7 @@ public SessionDataSetWrapper executeLastDataQueryForOneDevice( | |
| SessionDataSet resp = | ||
| session.executeLastDataQueryForOneDevice(db, device, sensors, isLegalPathNodes); | ||
| SessionDataSetWrapper wrapper = new SessionDataSetWrapper(resp, session, this); | ||
| occupy(session); | ||
| occupy(session, wrapper); | ||
| return wrapper; | ||
| } catch (IoTDBConnectionException e) { | ||
| // TException means the connection is broken, remove it and get a new one. | ||
|
|
@@ -3344,7 +3363,7 @@ public SessionDataSetWrapper executeAggregationQuery( | |
| try { | ||
| SessionDataSet resp = session.executeAggregationQuery(paths, aggregations); | ||
| SessionDataSetWrapper wrapper = new SessionDataSetWrapper(resp, session, this); | ||
| occupy(session); | ||
| occupy(session, wrapper); | ||
| return wrapper; | ||
| } catch (IoTDBConnectionException e) { | ||
| // TException means the connection is broken, remove it and get a new one. | ||
|
|
@@ -3373,7 +3392,7 @@ public SessionDataSetWrapper executeAggregationQuery( | |
| SessionDataSet resp = | ||
| session.executeAggregationQuery(paths, aggregations, startTime, endTime); | ||
| SessionDataSetWrapper wrapper = new SessionDataSetWrapper(resp, session, this); | ||
| occupy(session); | ||
| occupy(session, wrapper); | ||
| return wrapper; | ||
| } catch (IoTDBConnectionException e) { | ||
| // TException means the connection is broken, remove it and get a new one. | ||
|
|
@@ -3406,7 +3425,7 @@ public SessionDataSetWrapper executeAggregationQuery( | |
| SessionDataSet resp = | ||
| session.executeAggregationQuery(paths, aggregations, startTime, endTime, interval); | ||
| SessionDataSetWrapper wrapper = new SessionDataSetWrapper(resp, session, this); | ||
| occupy(session); | ||
| occupy(session, wrapper); | ||
| return wrapper; | ||
| } catch (IoTDBConnectionException e) { | ||
| // TException means the connection is broken, remove it and get a new one. | ||
|
|
@@ -3441,7 +3460,7 @@ public SessionDataSetWrapper executeAggregationQuery( | |
| session.executeAggregationQuery( | ||
| paths, aggregations, startTime, endTime, interval, slidingStep); | ||
| SessionDataSetWrapper wrapper = new SessionDataSetWrapper(resp, session, this); | ||
| occupy(session); | ||
| occupy(session, wrapper); | ||
| return wrapper; | ||
| } catch (IoTDBConnectionException e) { | ||
| // TException means the connection is broken, remove it and get a new one. | ||
|
|
@@ -3491,7 +3510,7 @@ public void setFetchSize(int fetchSize) { | |
| for (ISession session : queue) { | ||
| session.setFetchSize(fetchSize); | ||
| } | ||
| for (ISession session : occupied.keySet()) { | ||
| for (ISession session : occupied) { | ||
| session.setFetchSize(fetchSize); | ||
| } | ||
| } | ||
|
|
@@ -3522,7 +3541,7 @@ public void setTimeZone(String zoneId) | |
| for (ISession session : queue) { | ||
| session.setTimeZoneOfSession(zoneId); | ||
| } | ||
| for (ISession session : occupied.keySet()) { | ||
| for (ISession session : occupied) { | ||
| session.setTimeZoneOfSession(zoneId); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Unrelated to this PR, but in the same method: the retry loop above never |
||
| } | ||
| } | ||
|
|
@@ -3552,7 +3571,7 @@ public void setEnableRedirection(boolean enableRedirection) { | |
| for (ISession session : queue) { | ||
| session.setEnableRedirection(enableRedirection); | ||
| } | ||
| for (ISession session : occupied.keySet()) { | ||
| for (ISession session : occupied) { | ||
| session.setEnableRedirection(enableRedirection); | ||
| } | ||
| } | ||
|
|
@@ -3568,7 +3587,7 @@ public void setEnableQueryRedirection(boolean enableQueryRedirection) { | |
| for (ISession session : queue) { | ||
| session.setEnableQueryRedirection(enableQueryRedirection); | ||
| } | ||
| for (ISession session : occupied.keySet()) { | ||
| for (ISession session : occupied) { | ||
| session.setEnableQueryRedirection(enableQueryRedirection); | ||
| } | ||
| } | ||
|
|
@@ -3630,7 +3649,7 @@ public void setVersion(Version version) { | |
| for (ISession session : queue) { | ||
| session.setVersion(version); | ||
| } | ||
| for (ISession session : occupied.keySet()) { | ||
| for (ISession session : occupied) { | ||
| session.setVersion(version); | ||
| } | ||
| } | ||
|
|
@@ -3646,7 +3665,7 @@ public void setQueryTimeout(long timeoutInMs) { | |
| for (ISession session : queue) { | ||
| session.setQueryTimeout(timeoutInMs); | ||
| } | ||
| for (ISession session : occupied.keySet()) { | ||
| for (ISession session : occupied) { | ||
| session.setQueryTimeout(timeoutInMs); | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TableSessionWrapper.close()(not part of this diff) still bypasses this invariant. When restoringsql_dialector the database fails, it either callssession.close()directly (TableSessionWrapper.java L121, L137) or lets anIoTDBConnectionExceptionescape (L114, L131). In both cases the session never leavesoccupied, socurrentOccupiedSize()can still drift. In a unit test it stays at 1 after such a failure; master reports 0.Routing these branches through
sessionPool.cleanSessionAndMayThrowConnectionException(session)would keep the count right. It would also stop the pool from losing capacity, becausesizeis never decremented on those paths today.