Author: markt
Date: Thu Jan 24 22:54:41 2019
New Revision: 1852079

URL: http://svn.apache.org/viewvc?rev=1852079&view=rev
Log:
Fix https://bz.apache.org/bugzilla/show_bug.cgi?id=57974
Re-work code that supports Session.getOpenSessions() to ensure that both 
client-side and server-side calls behave as the EG intended

Added:
    
tomcat/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainerGetOpenSessions.java
   (with props)
Modified:
    tomcat/trunk/java/org/apache/tomcat/websocket/WsSession.java
    tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
    
tomcat/trunk/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java
    tomcat/trunk/java/org/apache/tomcat/websocket/server/WsServerContainer.java
    tomcat/trunk/webapps/docs/changelog.xml

Modified: tomcat/trunk/java/org/apache/tomcat/websocket/WsSession.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/WsSession.java?rev=1852079&r1=1852078&r2=1852079&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/WsSession.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/WsSession.java Thu Jan 24 
22:54:41 2019
@@ -45,6 +45,7 @@ import javax.websocket.RemoteEndpoint;
 import javax.websocket.SendResult;
 import javax.websocket.Session;
 import javax.websocket.WebSocketContainer;
+import javax.websocket.server.ServerEndpointConfig;
 
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
@@ -416,7 +417,7 @@ public class WsSession implements Sessio
     @Override
     public Set<Session> getOpenSessions() {
         checkState();
-        return webSocketContainer.getOpenSessions(localEndpoint);
+        return webSocketContainer.getOpenSessions(getSessionMapKey());
     }
 
 
@@ -605,11 +606,21 @@ public class WsSession implements Sessio
                 localEndpoint.onError(this, e);
             }
         } finally {
-            webSocketContainer.unregisterSession(localEndpoint, this);
+            webSocketContainer.unregisterSession(getSessionMapKey(), this);
         }
     }
 
 
+    private Object getSessionMapKey() {
+        if (endpointConfig instanceof ServerEndpointConfig) {
+            // Server
+            return ((ServerEndpointConfig) endpointConfig).getPath();
+        } else {
+            // Client
+            return localEndpoint;
+        }
+    }
+
     /**
      * Use protected so unit tests can access this method directly.
      * @param msg The message

Modified: 
tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java?rev=1852079&r1=1852078&r2=1852079&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java 
(original)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java Thu 
Jan 24 22:54:41 2019
@@ -89,8 +89,9 @@ public class WsWebSocketContainer implem
     private final Object asynchronousChannelGroupLock = new Object();
 
     private final Log log = LogFactory.getLog(WsWebSocketContainer.class); // 
must not be static
-    private final Map<Endpoint, Set<WsSession>> endpointSessionMap =
-            new HashMap<>();
+    // Server side uses the endpoint path as the key
+    // Client side uses the client endpoint instance
+    private final Map<Object, Set<WsSession>> endpointSessionMap = new 
HashMap<>();
     private final Map<WsSession,WsSession> sessions = new 
ConcurrentHashMap<>();
     private final Object endPointSessionMapLock = new Object();
 
@@ -578,7 +579,7 @@ public class WsWebSocketContainer implem
         return ByteBuffer.wrap(bytes);
     }
 
-    protected void registerSession(Endpoint endpoint, WsSession wsSession) {
+    protected void registerSession(Object key, WsSession wsSession) {
 
         if (!wsSession.isOpen()) {
             // The session was closed during onOpen. No need to register it.
@@ -588,10 +589,10 @@ public class WsWebSocketContainer implem
             if (endpointSessionMap.size() == 0) {
                 BackgroundProcessManager.getInstance().register(this);
             }
-            Set<WsSession> wsSessions = endpointSessionMap.get(endpoint);
+            Set<WsSession> wsSessions = endpointSessionMap.get(key);
             if (wsSessions == null) {
                 wsSessions = new HashSet<>();
-                endpointSessionMap.put(endpoint, wsSessions);
+                endpointSessionMap.put(key, wsSessions);
             }
             wsSessions.add(wsSession);
         }
@@ -599,14 +600,14 @@ public class WsWebSocketContainer implem
     }
 
 
-    protected void unregisterSession(Endpoint endpoint, WsSession wsSession) {
+    protected void unregisterSession(Object key, WsSession wsSession) {
 
         synchronized (endPointSessionMapLock) {
-            Set<WsSession> wsSessions = endpointSessionMap.get(endpoint);
+            Set<WsSession> wsSessions = endpointSessionMap.get(key);
             if (wsSessions != null) {
                 wsSessions.remove(wsSession);
                 if (wsSessions.size() == 0) {
-                    endpointSessionMap.remove(endpoint);
+                    endpointSessionMap.remove(key);
                 }
             }
             if (endpointSessionMap.size() == 0) {
@@ -617,10 +618,10 @@ public class WsWebSocketContainer implem
     }
 
 
-    Set<Session> getOpenSessions(Endpoint endpoint) {
+    Set<Session> getOpenSessions(Object key) {
         HashSet<Session> result = new HashSet<>();
         synchronized (endPointSessionMapLock) {
-            Set<WsSession> sessions = endpointSessionMap.get(endpoint);
+            Set<WsSession> sessions = endpointSessionMap.get(key);
             if (sessions != null) {
                 result.addAll(sessions);
             }

Modified: 
tomcat/trunk/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java?rev=1852079&r1=1852078&r2=1852079&view=diff
==============================================================================
--- 
tomcat/trunk/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java 
(original)
+++ 
tomcat/trunk/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java 
Thu Jan 24 22:54:41 2019
@@ -26,8 +26,8 @@ import javax.websocket.CloseReason;
 import javax.websocket.CloseReason.CloseCodes;
 import javax.websocket.DeploymentException;
 import javax.websocket.Endpoint;
-import javax.websocket.EndpointConfig;
 import javax.websocket.Extension;
+import javax.websocket.server.ServerEndpointConfig;
 
 import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler;
 import org.apache.juli.logging.Log;
@@ -54,7 +54,7 @@ public class WsHttpUpgradeHandler implem
     private SocketWrapperBase<?> socketWrapper;
 
     private Endpoint ep;
-    private EndpointConfig endpointConfig;
+    private ServerEndpointConfig serverEndpointConfig;
     private WsServerContainer webSocketContainer;
     private WsHandshakeRequest handshakeRequest;
     private List<Extension> negotiatedExtensions;
@@ -80,13 +80,13 @@ public class WsHttpUpgradeHandler implem
     }
 
 
-    public void preInit(Endpoint ep, EndpointConfig endpointConfig,
+    public void preInit(Endpoint ep, ServerEndpointConfig serverEndpointConfig,
             WsServerContainer wsc, WsHandshakeRequest handshakeRequest,
             List<Extension> negotiatedExtensionsPhase2, String subProtocol,
             Transformation transformation, Map<String,String> pathParameters,
             boolean secure) {
         this.ep = ep;
-        this.endpointConfig = endpointConfig;
+        this.serverEndpointConfig = serverEndpointConfig;
         this.webSocketContainer = wsc;
         this.handshakeRequest = handshakeRequest;
         this.negotiatedExtensions = negotiatedExtensionsPhase2;
@@ -124,14 +124,14 @@ public class WsHttpUpgradeHandler implem
                     handshakeRequest.getQueryString(),
                     handshakeRequest.getUserPrincipal(), httpSessionId,
                     negotiatedExtensions, subProtocol, pathParameters, secure,
-                    endpointConfig);
+                    serverEndpointConfig);
             wsFrame = new WsFrameServer(socketWrapper, wsSession, 
transformation,
                     applicationClassLoader);
             // WsFrame adds the necessary final transformations. Copy the
             // completed transformation chain to the remote end point.
             
wsRemoteEndpointServer.setTransformation(wsFrame.getTransformation());
-            ep.onOpen(wsSession, endpointConfig);
-            webSocketContainer.registerSession(ep, wsSession);
+            ep.onOpen(wsSession, serverEndpointConfig);
+            webSocketContainer.registerSession(serverEndpointConfig.getPath(), 
wsSession);
         } catch (DeploymentException e) {
             throw new IllegalArgumentException(e);
         } finally {

Modified: 
tomcat/trunk/java/org/apache/tomcat/websocket/server/WsServerContainer.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/server/WsServerContainer.java?rev=1852079&r1=1852078&r2=1852079&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/server/WsServerContainer.java 
(original)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/server/WsServerContainer.java 
Thu Jan 24 22:54:41 2019
@@ -37,7 +37,6 @@ import javax.websocket.CloseReason;
 import javax.websocket.CloseReason.CloseCodes;
 import javax.websocket.DeploymentException;
 import javax.websocket.Encoder;
-import javax.websocket.Endpoint;
 import javax.websocket.server.ServerContainer;
 import javax.websocket.server.ServerEndpoint;
 import javax.websocket.server.ServerEndpointConfig;
@@ -341,8 +340,8 @@ public class WsServerContainer extends W
      * Overridden to make it visible to other classes in this package.
      */
     @Override
-    protected void registerSession(Endpoint endpoint, WsSession wsSession) {
-        super.registerSession(endpoint, wsSession);
+    protected void registerSession(Object key, WsSession wsSession) {
+        super.registerSession(key, wsSession);
         if (wsSession.isOpen() &&
                 wsSession.getUserPrincipal() != null &&
                 wsSession.getHttpSessionId() != null) {
@@ -358,13 +357,13 @@ public class WsServerContainer extends W
      * Overridden to make it visible to other classes in this package.
      */
     @Override
-    protected void unregisterSession(Endpoint endpoint, WsSession wsSession) {
+    protected void unregisterSession(Object key, WsSession wsSession) {
         if (wsSession.getUserPrincipal() != null &&
                 wsSession.getHttpSessionId() != null) {
             unregisterAuthenticatedSession(wsSession,
                     wsSession.getHttpSessionId());
         }
-        super.unregisterSession(endpoint, wsSession);
+        super.unregisterSession(key, wsSession);
     }
 
 

Added: 
tomcat/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainerGetOpenSessions.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainerGetOpenSessions.java?rev=1852079&view=auto
==============================================================================
--- 
tomcat/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainerGetOpenSessions.java
 (added)
+++ 
tomcat/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainerGetOpenSessions.java
 Thu Jan 24 22:54:41 2019
@@ -0,0 +1,388 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.websocket;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.ServletContextEvent;
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.CloseReason;
+import javax.websocket.ContainerProvider;
+import javax.websocket.DeploymentException;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.servlets.DefaultServlet;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.tomcat.websocket.server.Constants;
+import org.apache.tomcat.websocket.server.WsContextListener;
+
+/*
+ * This method is split out into a separate class to make it easier to track 
the
+ * various permutations and combinations of client and server endpoints.
+ *
+ * Each test uses 2 client endpoint and 2 server endpoints with each client
+ * connecting to each server for a total of four connections (note sometimes
+ * the two clients and/or the two servers will be the sam)e.
+ */
+public class TestWsWebSocketContainerGetOpenSessions extends WebSocketBaseTest 
{
+
+    @Test
+    public void testClientAClientAPojoAPojoA() throws Exception {
+        Endpoint client1 = new ClientEndpointA();
+        Endpoint client2 = new ClientEndpointA();
+
+        doTest(client1, client2, "/pojoA", "/pojoA", 2, 2, 4, 4);
+    }
+
+
+    @Test
+    public void testClientAClientBPojoAPojoA() throws Exception {
+        Endpoint client1 = new ClientEndpointA();
+        Endpoint client2 = new ClientEndpointB();
+
+        doTest(client1, client2, "/pojoA", "/pojoA", 2, 2, 4, 4);
+    }
+
+
+    @Test
+    public void testClientAClientAPojoAPojoB() throws Exception {
+        Endpoint client1 = new ClientEndpointA();
+        Endpoint client2 = new ClientEndpointA();
+
+        doTest(client1, client2, "/pojoA", "/pojoB", 2, 2, 2, 2);
+    }
+
+
+    @Test
+    public void testClientAClientBPojoAPojoB() throws Exception {
+        Endpoint client1 = new ClientEndpointA();
+        Endpoint client2 = new ClientEndpointB();
+
+        doTest(client1, client2, "/pojoA", "/pojoB", 2, 2, 2, 2);
+    }
+
+
+    @Test
+    public void testClientAClientAProgAProgA() throws Exception {
+        Endpoint client1 = new ClientEndpointA();
+        Endpoint client2 = new ClientEndpointA();
+
+        doTest(client1, client2, "/progA", "/progA", 2, 2, 4, 4);
+    }
+
+
+    @Test
+    public void testClientAClientBProgAProgA() throws Exception {
+        Endpoint client1 = new ClientEndpointA();
+        Endpoint client2 = new ClientEndpointB();
+
+        doTest(client1, client2, "/progA", "/progA", 2, 2, 4, 4);
+    }
+
+
+    @Test
+    public void testClientAClientAProgAProgB() throws Exception {
+        Endpoint client1 = new ClientEndpointA();
+        Endpoint client2 = new ClientEndpointA();
+
+        doTest(client1, client2, "/progA", "/progB", 2, 2, 2, 2);
+    }
+
+
+    @Test
+    public void testClientAClientBProgAProgB() throws Exception {
+        Endpoint client1 = new ClientEndpointA();
+        Endpoint client2 = new ClientEndpointB();
+
+        doTest(client1, client2, "/progA", "/progB", 2, 2, 2, 2);
+    }
+
+
+    @Test
+    public void testClientAClientAPojoAProgA() throws Exception {
+        Endpoint client1 = new ClientEndpointA();
+        Endpoint client2 = new ClientEndpointA();
+
+        doTest(client1, client2, "/pojoA", "/progA", 2, 2, 2, 2);
+    }
+
+
+    @Test
+    public void testClientAClientBPojoAProgA() throws Exception {
+        Endpoint client1 = new ClientEndpointA();
+        Endpoint client2 = new ClientEndpointB();
+
+        doTest(client1, client2, "/pojoA", "/progA", 2, 2, 2, 2);
+    }
+
+
+    @Test
+    public void testClientAClientAPojoAProgB() throws Exception {
+        Endpoint client1 = new ClientEndpointA();
+        Endpoint client2 = new ClientEndpointA();
+
+        doTest(client1, client2, "/pojoA", "/progB", 2, 2, 2, 2);
+    }
+
+
+    @Test
+    public void testClientAClientBPojoAProgB() throws Exception {
+        Endpoint client1 = new ClientEndpointA();
+        Endpoint client2 = new ClientEndpointB();
+
+        doTest(client1, client2, "/pojoA", "/progB", 2, 2, 2, 2);
+    }
+
+
+    private void doTest(Endpoint client1, Endpoint client2, String server1, 
String server2,
+            int client1Count, int client2Count, int server1Count, int 
server2Count) throws Exception {
+        Tracker.reset();
+        Tomcat tomcat = getTomcatInstance();
+        // No file system docBase required
+        Context ctx = tomcat.addContext("", null);
+        ctx.addApplicationListener(Config.class.getName());
+        Tomcat.addServlet(ctx, "default", new DefaultServlet());
+        ctx.addServletMappingDecoded("/", "default");
+
+        tomcat.start();
+
+        WebSocketContainer wsContainer = 
ContainerProvider.getWebSocketContainer();
+
+        Session sClient1Server1 = createSession(wsContainer, client1, 
"client1", server1);
+        Session sClient1Server2 = createSession(wsContainer, client1, 
"client1", server2);
+        Session sClient2Server1 = createSession(wsContainer, client2, 
"client2", server1);
+        Session sClient2Server2 = createSession(wsContainer, client2, 
"client2", server2);
+
+
+        int delayCount = 0;
+        // Wait for up to 20s for this to complete. It should be a lot faster
+        // but some CI systems get be slow at times.
+        while (Tracker.getUpdateCount() < 8 && delayCount < 400) {
+            Thread.sleep(50);
+            delayCount++;
+        }
+
+        Assert.assertTrue(Tracker.checkRecord("client1", client1Count));
+        Assert.assertTrue(Tracker.checkRecord("client2", client2Count));
+        // Note: need to strip leading '/' from path
+        Assert.assertTrue(Tracker.checkRecord(server1.substring(1), 
server1Count));
+        Assert.assertTrue(Tracker.checkRecord(server2.substring(1), 
server2Count));
+
+        sClient1Server1.close();
+        sClient1Server2.close();
+        sClient2Server1.close();
+        sClient2Server2.close();
+    }
+
+
+    private Session createSession(WebSocketContainer wsContainer, Endpoint 
client,
+            String clientName, String server)
+            throws DeploymentException, IOException, URISyntaxException {
+
+        Session s = wsContainer.connectToServer(client,
+                ClientEndpointConfig.Builder.create().build(),
+                new URI("ws://localhost:" + getPort() + server));
+        Tracker.addRecord(clientName, s.getOpenSessions().size());
+        s.getBasicRemote().sendText("X");
+        return s;
+    }
+
+
+    public static class Config extends WsContextListener {
+
+        @Override
+        public void contextInitialized(ServletContextEvent sce) {
+            super.contextInitialized(sce);
+            ServerContainer sc =
+                    (ServerContainer) sce.getServletContext().getAttribute(
+                            
Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);
+
+            try {
+                sc.addEndpoint(PojoEndpointA.class);
+                sc.addEndpoint(PojoEndpointB.class);
+                sc.addEndpoint(ServerEndpointConfig.Builder.create(
+                        ServerEndpointA.class, "/progA").build());
+                sc.addEndpoint(ServerEndpointConfig.Builder.create(
+                        ServerEndpointB.class, "/progB").build());
+            } catch (DeploymentException e) {
+                throw new IllegalStateException(e);
+            }
+        }
+    }
+
+
+    public abstract static class PojoEndpointBase {
+
+        @OnMessage
+        public void onMessage(@SuppressWarnings("unused") String msg, Session 
session) {
+            Tracker.addRecord(getTrackingName(), 
session.getOpenSessions().size());
+        }
+
+        protected abstract String getTrackingName();
+    }
+
+
+    @ServerEndpoint("/pojoA")
+    public static class PojoEndpointA extends PojoEndpointBase {
+
+        @Override
+        protected String getTrackingName() {
+            return "pojoA";
+        }
+    }
+
+
+    @ServerEndpoint("/pojoB")
+    public static class PojoEndpointB extends PojoEndpointBase {
+
+        @Override
+        protected String getTrackingName() {
+            return "pojoB";
+        }
+    }
+
+
+    public abstract static class ServerEndpointBase extends Endpoint{
+
+        @Override
+        public void onOpen(Session session, EndpointConfig config) {
+            session.addMessageHandler(new TrackerMessageHandler(session, 
getTrackingName()));
+        }
+
+        protected abstract String getTrackingName();
+    }
+
+
+    public static final class ServerEndpointA extends ServerEndpointBase {
+
+        @Override
+        protected String getTrackingName() {
+            return "progA";
+        }
+    }
+
+
+    public static final class ServerEndpointB extends ServerEndpointBase {
+
+        @Override
+        protected String getTrackingName() {
+            return "progB";
+        }
+    }
+
+
+    public static final class TrackerMessageHandler implements 
MessageHandler.Whole<String> {
+
+        private final Session session;
+        private final String trackingName;
+
+        public TrackerMessageHandler(Session session, String trackingName) {
+            this.session = session;
+            this.trackingName = trackingName;
+        }
+
+        @Override
+        public void onMessage(String message) {
+            Tracker.addRecord(trackingName, session.getOpenSessions().size());
+        }
+    }
+
+
+    public abstract static class ClientEndpointBase extends Endpoint {
+
+        @Override
+        public void onOpen(Session session, EndpointConfig config) {
+            // NO-OP
+        }
+
+        @Override
+        public void onClose(Session session, CloseReason closeReason) {
+            // NO-OP
+        }
+
+        protected abstract String getTrackingName();
+    }
+
+
+    public static final class ClientEndpointA extends ClientEndpointBase {
+
+        @Override
+        protected String getTrackingName() {
+            return "clientA";
+        }
+    }
+
+
+    public static final class ClientEndpointB extends ClientEndpointBase {
+
+        @Override
+        protected String getTrackingName() {
+            return "clientB";
+        }
+    }
+
+
+    public static class Tracker {
+
+        private static final Map<String, Integer> records = new 
ConcurrentHashMap<>();
+        private static final AtomicInteger updateCount = new AtomicInteger(0);
+
+        public static void addRecord(String key, int count) {
+            records.put(key, Integer.valueOf(count));
+            updateCount.incrementAndGet();
+        }
+
+        public static boolean checkRecord(String key, int expectedCount) {
+            Integer actualCount = records.get(key);
+            if (actualCount == null) {
+                if (expectedCount == 0) {
+                    return true;
+                } else {
+                    return false;
+                }
+            } else {
+                return actualCount.intValue() == expectedCount;
+            }
+        }
+
+        public static int getUpdateCount() {
+            return updateCount.intValue();
+        }
+
+        public static void reset() {
+            records.clear();
+            updateCount.set(0);
+        }
+    }
+}

Propchange: 
tomcat/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainerGetOpenSessions.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: tomcat/trunk/webapps/docs/changelog.xml
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1852079&r1=1852078&r2=1852079&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/trunk/webapps/docs/changelog.xml Thu Jan 24 22:54:41 2019
@@ -167,6 +167,11 @@
   <subsection name="WebSocket">
     <changelog>
       <fix>
+        <bug>57974</bug>: Ensure implementation of
+        <code>Session.getOpenSessions()</code> returns correct value for both
+        client-side and server-side calls. (markt)
+      </fix>
+      <fix>
         <bug>63019</bug>: Use payload remaining bytes rather than limit when
         writing. Submitted by Benoit Courtilly. (remm)
       </fix>



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to