Repository: SeleniumHQ/selenium (language: python)

Issue:
[🐛 Bug]: Unexpected HTTP response status code 400 error when running tests inside chrome-standalone docker container
### What happened?

I recently upgraded to 4.26 from 4.23 and have been facing intermittent issue running tests inside a [Selenium Standalone chrome docker container v 130](https://hub.docker.com/r/selenium/standalone-chrome)

The point where this is happening is when I call `new Augmenter().augment(driver)`. However the same code works fine for a lot of tests before it starts failing. The exact error is `jdk.internal.net.http.websocket.CheckFailedException: Unexpected HTTP response status code 400`.

I am trying to intercept a HTTP Request (Response as well sometimes), but before I do that I have to augment the driver instance as its a `RemoteWebDriver` instance and that's when the exception is being thrown. I have not seen this happen when I am running it locally on a Windows 10 machine.

```
org.openqa.selenium.remote.http.ConnectionFailedException: JdkWebSocket initial request execution error
Build info: version: '4.26.0', revision: '8ccf0219d7'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '4.18.0-553.22.1.el8_10.x86_64', java.version: '11.0.25'
Driver info: driver.version: unknown
	at org.openqa.selenium.remote.http.jdk.JdkHttpClient.openSocket(JdkHttpClient.java:249)
	at org.openqa.selenium.devtools.Connection.<init>(Connection.java:89)
	at org.openqa.selenium.devtools.SeleniumCdpConnection.<init>(SeleniumCdpConnection.java:36)
	at org.openqa.selenium.devtools.SeleniumCdpConnection.lambda$create$3(SeleniumCdpConnection.java:103)
	at java.base/java.util.Optional.map(Optional.java:265)
	at org.openqa.selenium.devtools.SeleniumCdpConnection.create(SeleniumCdpConnection.java:103)
	at org.openqa.selenium.devtools.SeleniumCdpConnection.create(SeleniumCdpConnection.java:49)
	at org.openqa.selenium.devtools.DevToolsProvider.getImplementation(DevToolsProvider.java:50)
	at org.openqa.selenium.devtools.DevToolsProvider.getImplementation(DevToolsProvider.java:29)
	at org.openqa.selenium.remote.Augmenter.augment(Augmenter.java:207)
	at org.openqa.selenium.remote.Augmenter.augment(Augmenter.java:178)
	at com.jcs.race.steps.BaseSteps.interceptResponseEndingWith(BaseSteps.java:207)
	at com.jcs.race.steps.SearchSteps.lambda$new$0(SearchSteps.java:32)
	at ✽.I search for a valid customer(file:///app/bygg/teamcity-agent/Search.feature:5)
Caused by: java.net.http.WebSocketHandshakeException
	at java.net.http/jdk.internal.net.http.websocket.OpeningHandshake.resultFrom(OpeningHandshake.java:224)
	at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1072)
	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
	at java.base/java.util.concurrent.CompletableFuture.postFire(CompletableFuture.java:610)
	at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:840)
	at java.base/java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:479)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)
Caused by: jdk.internal.net.http.websocket.CheckFailedException: Unexpected HTTP response status code 400
	at java.net.http/jdk.internal.net.http.websocket.OpeningHandshake.checkFailed(OpeningHandshake.java:341)
	at java.net.http/jdk.internal.net.http.websocket.OpeningHandshake.handleResponse(OpeningHandshake.java:250)
	at java.net.http/jdk.internal.net.http.websocket.OpeningHandshake.resultFrom(OpeningHandshake.java:220)
	... 10 more
```

### How can we reproduce the issue?

```shell
private CommandExecutor getShutterBugExecutor() {
    Tracer tracer = OpenTelemetryTracer.getInstance();
    ClientConfig config = ClientConfig.defaultConfig().baseUrl("http://localhost:4444");
    CommandExecutor executor =
        new HttpCommandExecutor(
            Collections.emptyMap(),
            config,
            new TracedHttpClient.Factory(tracer, Factory.createDefault()));
    return new TracedCommandExecutor(executor, tracer);
  }

  @Bean
  private WebDriver setupChrome() {
    ChromeOptions options = new ChromeOptions();
    options.addArguments(List.of(
                "--enable-automation",
                "--disable-extensions",
                "--disable-client-side-phishing-detection",
                "--no-default-browser-check",
                "--no-first-run",
                "--enable-logging=stderr",
                "--log-level=0",
                "--disable-dev-shm-usage",
                "--user-data-dir=/home/seluser/.config/google-chrome/default"
    ));
    ImmutableCapabilities capabilities = new ImmutableCapabilities("browserName", "chrome");
    return new RemoteWebDriver(getShutterBugExecutor(), capabilities.merge(options));
  }

// The above bean is autowired into tests

// The below is a method in the test which uses this instance of driver:
protected DevTools interceptRequestEndingWith(String urlPrefix) {
    driver = new Augmenter().augment(driver);    /*<--This is the point of failure*/
    DevTools devTools = ((HasDevTools) driver).getDevTools();
    devTools.createSessionIfThereIsNotOne();
    devTools.send(Network.enable(Optional.empty(), Optional.empty(), Optional.empty()));
    devTools.addListener(
        Network.requestWillBeSent(),
        requestWillBeSent -> {
          Request request = requestWillBeSent.getRequest();
          if (request.getUrl().endsWith(urlPrefix)
              && !request.getUrl().contains("www.google-analytics.com")) {
            scenarioContext.requestBody = request.getPostData().get();
          }
        });
    return devTools;
  }

// The same method works for close to 20 mins before it fails.
```


### Relevant log output

```shell
org.openqa.selenium.remote.http.ConnectionFailedException: JdkWebSocket initial request execution error
Build info: version: '4.26.0', revision: '8ccf0219d7'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '4.18.0-553.22.1.el8_10.x86_64', java.version: '11.0.25'
Driver info: driver.version: unknown
	at org.openqa.selenium.remote.http.jdk.JdkHttpClient.openSocket(JdkHttpClient.java:249)
	at org.openqa.selenium.devtools.Connection.<init>(Connection.java:89)
	at org.openqa.selenium.devtools.SeleniumCdpConnection.<init>(SeleniumCdpConnection.java:36)
	at org.openqa.selenium.devtools.SeleniumCdpConnection.lambda$create$3(SeleniumCdpConnection.java:103)
	at java.base/java.util.Optional.map(Optional.java:265)
	at org.openqa.selenium.devtools.SeleniumCdpConnection.create(SeleniumCdpConnection.java:103)
	at org.openqa.selenium.devtools.SeleniumCdpConnection.create(SeleniumCdpConnection.java:49)
	at org.openqa.selenium.devtools.DevToolsProvider.getImplementation(DevToolsProvider.java:50)
	at org.openqa.selenium.devtools.DevToolsProvider.getImplementation(DevToolsProvider.java:29)
	at org.openqa.selenium.remote.Augmenter.augment(Augmenter.java:207)
	at org.openqa.selenium.remote.Augmenter.augment(Augmenter.java:178)
	at com.jcs.race.steps.BaseSteps.interceptResponseEndingWith(BaseSteps.java:207)
	at com.jcs.race.steps.SearchSteps.lambda$new$0(SearchSteps.java:32)
	at ✽.I search for a valid customer(file:///app/bygg/teamcity-agent/Search.feature:5)
Caused by: java.net.http.WebSocketHandshakeException
	at java.net.http/jdk.internal.net.http.websocket.OpeningHandshake.resultFrom(OpeningHandshake.java:224)
	at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1072)
	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
	at java.base/java.util.concurrent.CompletableFuture.postFire(CompletableFuture.java:610)
	at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:840)
	at java.base/java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:479)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)
Caused by: jdk.internal.net.http.websocket.CheckFailedException: Unexpected HTTP response status code 400
	at java.net.http/jdk.internal.net.http.websocket.OpeningHandshake.checkFailed(OpeningHandshake.java:341)
	at java.net.http/jdk.internal.net.http.websocket.OpeningHandshake.handleResponse(OpeningHandshake.java:250)
	at java.net.http/jdk.internal.net.http.websocket.OpeningHandshake.resultFrom(OpeningHandshake.java:220)
	... 10 more
```


### Operating System

RHEL8

### Selenium version

Java 4.26

### What are the browser(s) and version(s) where you see this issue?

Chrome 130

### What are the browser driver(s) and version(s) where you see this issue?

chromedriver 130

### Are you using Selenium Grid?

_No response_
[grid] limit the number of websocket connections per session
### **User description**
### Description
This change will add a limit of 10 websocket connections per session.
The `NodeOptions` / `NodeFlags` do allow to overwrite this default value. 

### Motivation and Context
Usually a client should only open a small number of websocket connections per session.
But there is no limit so a single session can block a lot of resources when connecting again and again.

@diemol this is the PR related to my email and 5bac4795ff6acc9f1ca1a6436aea0970ff98fb07 does at least ensure as soon as the client closes the websocket connection, the connection between the hub and the node / the node and the driver is released.

### Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)

### Checklist
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [x] I have read the [contributing](https://github.com/SeleniumHQ/selenium/blob/trunk/CONTRIBUTING.md) document.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
<!--- Provide a general summary of your changes in the Title above -->


___

### **PR Type**
enhancement, tests


___

### **Description**
- Introduced a limit on the number of websocket connections per session to improve resource management.
- Added a new POST endpoint to check and enforce connection limits in the Node class.
- Implemented connection limit logic in various node types including LocalNode, OneShotNode, and RemoteNode.
- Updated configuration options to allow setting a connection limit per session.
- Enhanced session management by adding connection counters and resetting them appropriately.
- Added test stubs to support future testing of connection limit logic.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><details><summary>7 files</summary><table>
<tr>
  <td>
    <details>
      <summary><strong>Node.java</strong><dd><code>Add websocket connection limit management in Node</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

java/src/org/openqa/selenium/grid/node/Node.java

<li>Added a new POST endpoint to check websocket connection limits per <br>session.<br> <li> Introduced <code>tryAcquireConnection</code> method to manage connection limits.<br>


</details>


  </td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/14410/files#diff-9724970402f0d888442caa09bf30d6f8074edde726dae3e98a52eda4f2ed8462">+11/-0</a>&nbsp; &nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
      <summary><strong>ProxyNodeWebsockets.java</strong><dd><code>Implement websocket connection limit check in ProxyNodeWebsockets</code></dd></summary>
<hr>

java/src/org/openqa/selenium/grid/node/ProxyNodeWebsockets.java

<li>Implemented connection limit check before allowing new websocket <br>connections.<br> <li> Added logging for excessive websocket connections.<br>


</details>


  </td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/14410/files#diff-d21ec9d4eb005abb0a30dfc5c4c0c4190c9d522243155ecae77b43a80a77b5bf">+7/-0</a>&nbsp; &nbsp; &nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
      <summary><strong>TryAcquireConnection.java</strong><dd><code>Create TryAcquireConnection class for connection limit handling</code></dd></summary>
<hr>

java/src/org/openqa/selenium/grid/node/TryAcquireConnection.java

<li>Created <code>TryAcquireConnection</code> class to handle connection limit <br>requests.<br> <li> Implemented HTTP handler for connection limit checks.<br>


</details>


  </td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/14410/files#diff-865edb4b252ede626b05ec3e5cde504f89a8e2b8d804990cc8af8a69d6829dce">+45/-0</a>&nbsp; &nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
      <summary><strong>OneShotNode.java</strong><dd><code>Integrate connection limit logic in OneShotNode</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

java/src/org/openqa/selenium/grid/node/k8s/OneShotNode.java

<li>Integrated connection limit logic in OneShotNode.<br> <li> Added atomic counter for connection tracking.<br>


</details>


  </td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/14410/files#diff-e2faca1f36057dbd7099e2881d61cd68301db4b5f6c2b1ac616b2189979f388e">+13/-2</a>&nbsp; &nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
      <summary><strong>LocalNode.java</strong><dd><code>Implement connection limit logic in LocalNode</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

java/src/org/openqa/selenium/grid/node/local/LocalNode.java

<li>Implemented connection limit logic in LocalNode.<br> <li> Added atomic counter for managing connections.<br>


</details>


  </td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/14410/files#diff-edc2e1943a5d89fa45151a9d214cc608551c61101f237ca9a5993d6dd157b57b">+31/-2</a>&nbsp; &nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
      <summary><strong>SessionSlot.java</strong><dd><code>Add and manage connection counter in SessionSlot</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

java/src/org/openqa/selenium/grid/node/local/SessionSlot.java

<li>Added connection counter to SessionSlot.<br> <li> Reset connection counter when session stops.<br>


</details>


  </td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/14410/files#diff-f6f6aa2f002118f5a983cd076f893c5a56c2f5f5d4c0d09d6f296c45b8b3ef30">+9/-0</a>&nbsp; &nbsp; &nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
      <summary><strong>RemoteNode.java</strong><dd><code>Implement remote connection limit check in RemoteNode</code>&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

java/src/org/openqa/selenium/grid/node/remote/RemoteNode.java

- Implemented remote connection limit check in RemoteNode.



</details>


  </td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/14410/files#diff-7d02354cb02076c23a0bda07882c1fd634c19eee19be14b96a4b77cd796cde75">+12/-0</a>&nbsp; &nbsp; </td>

</tr>                    
</table></details></td></tr><tr><td><strong>Configuration changes</strong></td><td><details><summary>3 files</summary><table>
<tr>
  <td>
    <details>
      <summary><strong>NodeFlags.java</strong><dd><code>Add connection limit per session parameter in NodeFlags</code>&nbsp; &nbsp; </dd></summary>
<hr>

java/src/org/openqa/selenium/grid/node/config/NodeFlags.java

<li>Added a new parameter for connection limit per session.<br> <li> Updated configuration to include connection limit settings.<br>


</details>


  </td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/14410/files#diff-33dd4982601ad783488b7633972c9c4cb1650d3b41ea53ed8bee3159649660be">+9/-0</a>&nbsp; &nbsp; &nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
      <summary><strong>NodeOptions.java</strong><dd><code>Define and retrieve default connection limit in NodeOptions</code></dd></summary>
<hr>

java/src/org/openqa/selenium/grid/node/config/NodeOptions.java

<li>Defined default connection limit for sessions.<br> <li> Added method to retrieve connection limit from configuration.<br>


</details>


  </td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/14410/files#diff-6e8d739c2d4c51a731e66ed587b852c2788e19b8fd325b3c90fb0bad5e53e594">+10/-0</a>&nbsp; &nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
      <summary><strong>LocalNodeFactory.java</strong><dd><code>Configure LocalNode with connection limit settings</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

java/src/org/openqa/selenium/grid/node/local/LocalNodeFactory.java

- Configured LocalNode to use connection limit settings.



</details>


  </td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/14410/files#diff-e110f9dbba166763066fd804c0405b76ec78b831950a1229b40b3ac7a8154a84">+2/-1</a>&nbsp; &nbsp; &nbsp; </td>

</tr>                    
</table></details></td></tr><tr><td><strong>Tests</strong></td><td><details><summary>1 files</summary><table>
<tr>
  <td>
    <details>
      <summary><strong>AddingNodesTest.java</strong><dd><code>Add test stub for connection limit logic in AddingNodesTest</code></dd></summary>
<hr>

java/test/org/openqa/selenium/grid/distributor/AddingNodesTest.java

- Added test stub for connection limit logic in AddingNodesTest.



</details>


  </td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/14410/files#diff-24b972fe88b1e515a226b8c3b1cd68eb9a0801a5584d83321c820425fb2cd254">+5/-0</a>&nbsp; &nbsp; &nbsp; </td>

</tr>                    
</table></details></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools and their descriptions




Discussion / hints:


@jonn-set, thank you for creating this issue. We will troubleshoot it as soon as we can.

---
<details>
  <summary>Info for maintainers</summary>
  <div>
    <br>
    <p>
      Triage this issue by using labels.
    </p>
    <p>
      If information is missing, add a helpful comment and then <code>I-issue-template</code> label.
    </p>
    <p>
      If the issue is a question, add the <code>I-question</code> label.
    </p>
    <p>
      If the issue is valid but there is no time to troubleshoot it, consider adding the <code>help wanted</code> label.
    </p>
    <p>
      If the issue requires changes or fixes from an external project (e.g., ChromeDriver, GeckoDriver, MSEdgeDriver, W3C),
     add the applicable <code>G-*</code> label, and it will provide the correct link and auto-close the
     issue.
    </p>
    <p>
      After troubleshooting the issue, please add the <code>R-awaiting answer</code> label.
    </p>
    <p>
      Thank you!
    </p>
  </div>
</details>



#14410 did limit the number of websocket connections per session, i think you hit this limit.
It might be better to change this to a limit of concurrent connections per session.
## PR Reviewer Guide 🔍

<table>
<tr><td>⏱️&nbsp;<strong>Estimated effort to review</strong>: 3 🔵🔵🔵⚪⚪</td></tr>
<tr><td>🧪&nbsp;<strong>No relevant tests</strong></td></tr>
<tr><td>🔒&nbsp;<strong>No security concerns identified</strong></td></tr>
<tr><td>⚡&nbsp;<strong>Key issues to review</strong><br><br>

<a href='https://github.com/SeleniumHQ/selenium/pull/14410/files#diff-edc2e1943a5d89fa45151a9d214cc608551c61101f237ca9a5993d6dd157b57bR596-R596'><strong>Possible Race Condition</strong></a><br>The `tryAcquireConnection` method uses `getAndIncrement()` on the connection counter, which may lead to a race condition if the limit is reached exactly.

<a href='https://github.com/SeleniumHQ/selenium/pull/14410/files#diff-e2faca1f36057dbd7099e2881d61cd68301db4b5f6c2b1ac616b2189979f388eR368-R368'><strong>Possible Race Condition</strong></a><br>Similar to the LocalNode issue, the `tryAcquireConnection` method uses `getAndIncrement()` which may lead to a race condition.

</td></tr>
</table>

## PR Code Suggestions ✨

<!-- be01115 -->

<table><thead><tr><td>Category</td><td align=left>Suggestion&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </td><td align=center>Score</td></tr><tbody><tr><td rowspan=1><strong>Enhancement</strong></td>
<td>



<details><summary>Implement a method to release connections and improve the connection acquisition logic<!-- not_implemented --></summary>

___


**Implement a method to release connections when they are no longer needed, to prevent <br>the connection counter from continuously increasing and potentially reaching the <br>limit even after connections are closed.**

[java/src/org/openqa/selenium/grid/node/local/LocalNode.java [581-597]](https://github.com/SeleniumHQ/selenium/pull/14410/files#diff-edc2e1943a5d89fa45151a9d214cc608551c61101f237ca9a5993d6dd157b57bR581-R597)

```diff
 @Override
 public boolean tryAcquireConnection(SessionId id) throws NoSuchSessionException {
   SessionSlot slot = currentSessions.getIfPresent(id);
 
   if (slot == null) {
     return false;
   }
 
   if (connectionLimitPerSession == -1) {
     // no limit
     return true;
   }
 
   AtomicLong counter = slot.getConnectionCounter();
 
-  return connectionLimitPerSession > counter.getAndIncrement();
+  long currentCount = counter.get();
+  while (currentCount < connectionLimitPerSession) {
+    if (counter.compareAndSet(currentCount, currentCount + 1)) {
+      return true;
+    }
+    currentCount = counter.get();
+  }
+  return false;
 }
 
+public void releaseConnection(SessionId id) {
+  SessionSlot slot = currentSessions.getIfPresent(id);
+  if (slot != null) {
+    AtomicLong counter = slot.getConnectionCounter();
+    counter.updateAndGet(count -> Math.max(0, count - 1));
+  }
+}
+
```                   

- [ ] **Apply this suggestion** <!-- /improve --apply_suggestion=0 -->

<details><summary>Suggestion importance[1-10]: 8</summary>

Why: Adding a method to release connections is a significant improvement that prevents the counter from increasing indefinitely, which could lead to reaching the limit even after connections are closed.


</details></details></td><td align=center>8

</td></tr><tr><td rowspan=2><strong>Performance</strong></td>
<td>



<details><summary>Use a more efficient data structure for managing connection counters across multiple sessions<!-- not_implemented --></summary>

___


**Consider using a more efficient data structure for managing connection counters, <br>such as a ConcurrentHashMap<SessionId, AtomicLong>, instead of storing the counter <br>in the SessionSlot. This would allow for better scalability and easier management of <br>connection limits across multiple sessions.**

[java/src/org/openqa/selenium/grid/node/local/LocalNode.java [581-597]](https://github.com/SeleniumHQ/selenium/pull/14410/files#diff-edc2e1943a5d89fa45151a9d214cc608551c61101f237ca9a5993d6dd157b57bR581-R597)

```diff
+private final ConcurrentHashMap<SessionId, AtomicLong> connectionCounters = new ConcurrentHashMap<>();
+
 @Override
 public boolean tryAcquireConnection(SessionId id) throws NoSuchSessionException {
   SessionSlot slot = currentSessions.getIfPresent(id);
 
   if (slot == null) {
     return false;
   }
 
   if (connectionLimitPerSession == -1) {
     // no limit
     return true;
   }
 
-  AtomicLong counter = slot.getConnectionCounter();
+  AtomicLong counter = connectionCounters.computeIfAbsent(id, k -> new AtomicLong());
 
   return connectionLimitPerSession > counter.getAndIncrement();
 }
 
```                   

- [ ] **Apply this suggestion** <!-- /improve --apply_suggestion=1 -->

<details><summary>Suggestion importance[1-10]: 7</summary>

Why: The suggestion to use a ConcurrentHashMap for managing connection counters is valid as it can improve scalability and management across sessions. However, the current implementation using AtomicLong in SessionSlot is already efficient for the existing use case.


</details></details></td><td align=center>7

</td></tr><tr><td>



<details><summary>Add a configurable delay when the connection limit is reached to prevent rapid repeated attempts<!-- not_implemented --></summary>

___


**Consider adding a configurable delay or backoff mechanism when the connection limit <br>is reached, to prevent rapid repeated attempts that could overload the system.**

[java/src/org/openqa/selenium/grid/node/local/LocalNode.java [581-597]](https://github.com/SeleniumHQ/selenium/pull/14410/files#diff-edc2e1943a5d89fa45151a9d214cc608551c61101f237ca9a5993d6dd157b57bR581-R597)

```diff
+private final Duration backoffDelay = Duration.ofMillis(100);
+
 @Override
 public boolean tryAcquireConnection(SessionId id) throws NoSuchSessionException {
   SessionSlot slot = currentSessions.getIfPresent(id);
 
   if (slot == null) {
     return false;
   }
 
   if (connectionLimitPerSession == -1) {
     // no limit
     return true;
   }
 
   AtomicLong counter = slot.getConnectionCounter();
+  long currentCount = counter.get();
 
-  return connectionLimitPerSession > counter.getAndIncrement();
+  while (currentCount < connectionLimitPerSession) {
+    if (counter.compareAndSet(currentCount, currentCount + 1)) {
+      return true;
+    }
+    currentCount = counter.get();
+  }
+
+  // Connection limit reached, apply backoff
+  try {
+    Thread.sleep(backoffDelay.toMillis());
+  } catch (InterruptedException e) {
+    Thread.currentThread().interrupt();
+  }
+
+  return false;
 }
 
```                   

- [ ] **Apply this suggestion** <!-- /improve --apply_suggestion=2 -->

<details><summary>Suggestion importance[1-10]: 7</summary>

Why: Introducing a backoff mechanism can prevent system overload from rapid repeated attempts, enhancing performance and stability, but it is not essential for the current implementation. 


</details></details></td><td align=center>7

</td></tr><tr><td rowspan=1><strong>Maintainability</strong></td>
<td>



<details><summary>Add logging statements to track connection acquisition and release<!-- not_implemented --></summary>

___


**Add logging statements to track connection acquisition and release, which will help <br>with debugging and monitoring the connection limit feature.**

[java/src/org/openqa/selenium/grid/node/local/LocalNode.java [581-597]](https://github.com/SeleniumHQ/selenium/pull/14410/files#diff-edc2e1943a5d89fa45151a9d214cc608551c61101f237ca9a5993d6dd157b57bR581-R597)

```diff
+private static final Logger LOG = Logger.getLogger(LocalNode.class.getName());
+
 @Override
 public boolean tryAcquireConnection(SessionId id) throws NoSuchSessionException {
   SessionSlot slot = currentSessions.getIfPresent(id);
 
   if (slot == null) {
+    LOG.fine("No session found for id: " + id);
     return false;
   }
 
   if (connectionLimitPerSession == -1) {
-    // no limit
+    LOG.fine("No connection limit for session: " + id);
     return true;
   }
 
   AtomicLong counter = slot.getConnectionCounter();
+  long newCount = counter.incrementAndGet();
 
-  return connectionLimitPerSession > counter.getAndIncrement();
+  if (newCount <= connectionLimitPerSession) {
+    LOG.fine("Connection acquired for session " + id + ". Current count: " + newCount);
+    return true;
+  } else {
+    counter.decrementAndGet();
+    LOG.warning("Connection limit reached for session " + id + ". Current count: " + (newCount - 1));
+    return false;
+  }
 }
 
```                   

- [ ] **Apply this suggestion** <!-- /improve --apply_suggestion=3 -->

<details><summary>Suggestion importance[1-10]: 6</summary>

Why: Adding logging statements can aid in debugging and monitoring, which is beneficial for maintainability, but it is not a critical change.


</details></details></td><td align=center>6

</td></tr></tr></tbody></table>


## CI Failure Feedback 🧐

#### (Checks updated until commit https://github.com/SeleniumHQ/selenium/commit/0b5e1c81a8bf649066569265fd117d5c5d04d80f)


<table><tr><td>

**Action:** Ruby / Remote Tests (edge, windows) / Remote Tests (edge, windows)</td></tr>
<tr><td>

**Failed stage:** [Run Bazel](https://github.com/SeleniumHQ/selenium/actions/runs/11568796149/job/32201679199) [❌]

</td></tr>
<tr><td>

**Failed test name:** Selenium::WebDriver::Remote::Driver errors when not set


</td></tr>
<tr><td>

**Failure summary:**

The action failed because the test <code>Selenium::WebDriver::Remote::Driver errors when not set</code> did not <br>pass. The failure was due to:<br> <li> The test expected a <code>Selenium::WebDriver::Error::WebDriverError</code> with the message "You must enable <br>downloads in order to work with downloadable files."<br> <li> Instead, it received a <code>Selenium::WebDriver::Error::UnknownError</code> indicating "Cannot find downloads <br>file system for session id."<br>

</td></tr>
<tr><td>

<details><summary>Relevant error logs:</summary>


```yaml
1:  ##[group]Operating System
2:  Microsoft Windows Server 2022
...

681:  [32m[2,921 / 3,098][0m Extracting npm package @mui/icons-material@5.15.18_796719057; 29s local, disk-cache ... (4 actions, 2 running)
682:  [32m[2,932 / 3,098][0m Extracting npm package @mui/icons-material@5.15.18_796719057; 30s local, disk-cache ... (4 actions, 3 running)
683:  [32m[2,933 / 3,098][0m Extracting npm package @mui/icons-material@5.15.18_796719057; 33s local, disk-cache ... (4 actions, 2 running)
684:  [32m[2,940 / 3,098][0m Extracting npm package @mui/icons-material@5.15.18_796719057; 35s local, disk-cache ... (4 actions, 2 running)
685:  [32m[2,942 / 3,098][0m Extracting npm package @mui/icons-material@5.15.18_796719057; 36s local, disk-cache ... (4 actions, 2 running)
686:  [32m[2,957 / 3,098][0m Extracting npm package @mui/icons-material@5.15.18_796719057; 37s local, disk-cache ... (4 actions, 2 running)
687:  [32m[2,965 / 3,098][0m Extracting npm package @mui/icons-material@5.15.18_796719057; 38s local, disk-cache ... (4 actions, 3 running)
688:  [32mINFO: [0mFrom Building java/src/org/openqa/selenium/remote/libapi-class.jar (71 source files):
689:  java\src\org\openqa\selenium\remote\ErrorHandler.java:46: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
690:  private final ErrorCodes errorCodes;
691:  ^
692:  java\src\org\openqa\selenium\remote\ErrorHandler.java:60: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
693:  this.errorCodes = new ErrorCodes();
694:  ^
695:  java\src\org\openqa\selenium\remote\ErrorHandler.java:68: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
696:  public ErrorHandler(ErrorCodes codes, boolean includeServerErrors) {
697:  ^
698:  java\src\org\openqa\selenium\remote\Response.java:97: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
699:  ErrorCodes errorCodes = new ErrorCodes();
700:  ^
701:  java\src\org\openqa\selenium\remote\Response.java:97: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
702:  ErrorCodes errorCodes = new ErrorCodes();
703:  ^
704:  java\src\org\openqa\selenium\remote\ProtocolHandshake.java:181: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
705:  response.setStatus(ErrorCodes.SUCCESS);
706:  ^
707:  java\src\org\openqa\selenium\remote\ProtocolHandshake.java:182: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
708:  response.setState(ErrorCodes.SUCCESS_STRING);
709:  ^
710:  java\src\org\openqa\selenium\remote\W3CHandshakeResponse.java:53: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
711:  new ErrorCodes().toStatus((String) rawError, Optional.of(tuple.getStatusCode())));
712:  ^
713:  java\src\org\openqa\selenium\remote\W3CHandshakeResponse.java:56: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
714:  new ErrorCodes().getExceptionType((String) rawError);
715:  ^
716:  java\src\org\openqa\selenium\remote\codec\AbstractHttpResponseCodec.java:44: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
717:  private final ErrorCodes errorCodes = new ErrorCodes();
718:  ^
719:  java\src\org\openqa\selenium\remote\codec\AbstractHttpResponseCodec.java:44: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
720:  private final ErrorCodes errorCodes = new ErrorCodes();
721:  ^
722:  java\src\org\openqa\selenium\remote\codec\AbstractHttpResponseCodec.java:55: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
723:  int status = response.getStatus() == ErrorCodes.SUCCESS ? HTTP_OK : HTTP_INTERNAL_ERROR;
724:  ^
725:  java\src\org\openqa\selenium\remote\codec\AbstractHttpResponseCodec.java:101: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
726:  response.setStatus(ErrorCodes.UNKNOWN_COMMAND);
727:  ^
728:  java\src\org\openqa\selenium\remote\codec\AbstractHttpResponseCodec.java:103: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
729:  response.setStatus(ErrorCodes.UNHANDLED_ERROR);
730:  ^
731:  java\src\org\openqa\selenium\remote\codec\AbstractHttpResponseCodec.java:117: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
732:  response.setStatus(ErrorCodes.SUCCESS);
733:  ^
734:  java\src\org\openqa\selenium\remote\codec\AbstractHttpResponseCodec.java:118: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
735:  response.setState(errorCodes.toState(ErrorCodes.SUCCESS));
736:  ^
737:  java\src\org\openqa\selenium\remote\codec\AbstractHttpResponseCodec.java:124: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
738:  response.setState(errorCodes.toState(ErrorCodes.SUCCESS));
739:  ^
740:  java\src\org\openqa\selenium\remote\codec\w3c\W3CHttpResponseCodec.java:70: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
741:  private final ErrorCodes errorCodes = new ErrorCodes();
742:  ^
743:  java\src\org\openqa\selenium\remote\codec\w3c\W3CHttpResponseCodec.java:70: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
744:  private final ErrorCodes errorCodes = new ErrorCodes();
745:  ^
746:  java\src\org\openqa\selenium\remote\codec\w3c\W3CHttpResponseCodec.java:93: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
747:  response.setStatus(ErrorCodes.UNKNOWN_COMMAND);
748:  ^
749:  java\src\org\openqa\selenium\remote\codec\w3c\W3CHttpResponseCodec.java:98: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
750:  response.setStatus(ErrorCodes.UNHANDLED_ERROR);
751:  ^
752:  java\src\org\openqa\selenium\remote\codec\w3c\W3CHttpResponseCodec.java:145: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
753:  response.setStatus(ErrorCodes.SUCCESS);
...

915:  [32m[3,098 / 3,122][0m Testing //rb/spec/integration/selenium/webdriver:manager-edge-remote; 8s disk-cache ... (4 actions, 0 running)
916:  [32m[3,098 / 3,122][0m Testing //rb/spec/integration/selenium/webdriver:manager-edge-remote; 36s disk-cache ... (4 actions, 0 running)
917:  [32m[3,098 / 3,122][0m Testing //rb/spec/integration/selenium/webdriver:element-edge-remote; 1s local, disk-cache ... (4 actions, 1 running)
918:  [32m[3,098 / 3,122][0m Testing //rb/spec/integration/selenium/webdriver:element-edge-remote; 2s local, disk-cache ... (4 actions, 1 running)
919:  [32m[3,098 / 3,122][0m Testing //rb/spec/integration/selenium/webdriver:element-edge-remote; 62s local, disk-cache ... (4 actions, 1 running)
920:  [32m[3,099 / 3,122][0m 1 / 29 tests;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver:shadow_root-edge-remote; 63s ... (4 actions, 1 running)
921:  [32m[3,099 / 3,122][0m 1 / 29 tests;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver:shadow_root-edge-remote; 73s ... (4 actions, 1 running)
922:  [32m[3,099 / 3,122][0m 1 / 29 tests;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver:shadow_root-edge-remote; 76s ... (4 actions, 1 running)
923:  [32m[3,099 / 3,122][0m 1 / 29 tests;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver:error-edge-remote; 79s ... (4 actions, 2 running)
924:  [32m[3,100 / 3,122][0m 2 / 29 tests;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver:error-edge-remote; 80s ... (4 actions, 1 running)
925:  [32m[3,100 / 3,122][0m 2 / 29 tests;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver:error-edge-remote; 90s ... (4 actions, 1 running)
926:  [32m[3,100 / 3,122][0m 2 / 29 tests;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver:error-edge-remote; 94s ... (4 actions, 1 running)
...

993:  [32m[3,115 / 3,122][0m 17 / 29 tests;[0m Testing //rb/spec/integration/selenium/webdriver/remote:driver-edge-remote; 167s local, disk-cache ... (4 actions, 2 running)
994:  [32m[3,115 / 3,122][0m 17 / 29 tests;[0m Testing //rb/spec/integration/selenium/webdriver/remote:driver-edge-remote; 170s local, disk-cache ... (4 actions, 2 running)
995:  [32m[3,115 / 3,122][0m 17 / 29 tests;[0m Testing //rb/spec/integration/selenium/webdriver/remote:driver-edge-remote; 172s local, disk-cache ... (4 actions, 2 running)
996:  [32m[3,116 / 3,122][0m 18 / 29 tests;[0m Testing //rb/spec/integration/selenium/webdriver/remote:driver-edge-remote; 174s local, disk-cache ... (4 actions, 1 running)
997:  [32m[3,116 / 3,122][0m 18 / 29 tests;[0m Testing //rb/spec/integration/selenium/webdriver/remote:driver-edge-remote; 184s local, disk-cache ... (4 actions, 1 running)
998:  [32m[3,116 / 3,122][0m 18 / 29 tests;[0m Testing //rb/spec/integration/selenium/webdriver/remote:driver-edge-remote; 187s local, disk-cache ... (4 actions, 1 running)
999:  [32m[3,116 / 3,122][0m 18 / 29 tests;[0m Testing //rb/spec/integration/selenium/webdriver/remote:driver-edge-remote; 200s local, disk-cache ... (4 actions, 2 running)
1000:  [31m[1mFAIL: [0m//rb/spec/integration/selenium/webdriver/remote:driver-edge-remote (see D:/_bazel/execroot/_main/bazel-out/x64_windows-fastbuild/testlogs/rb/spec/integration/selenium/webdriver/remote/driver-edge-remote/test.log)
1001:  [31m[1mFAILED: [0m//rb/spec/integration/selenium/webdriver/remote:driver-edge-remote (Summary)
...

1016:  Selenium::WebDriver::Remote::Driver
1017:  exposes session_id
1018:  exposes remote status
1019:  Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead
1020:  uses a default file detector
1021:  lists downloads
1022:  downloads a file
1023:  deletes downloadable files
1024:  errors when not set (FAILED - 1)
1025:  Failures:
1026:  1) Selenium::WebDriver::Remote::Driver errors when not set
1027:  Failure/Error:
1028:  expect {
1029:  driver.downloadable_files
1030:  }.to raise_exception(Error::WebDriverError,
1031:  'You must enable downloads in order to work with downloadable files.')
1032:  expected Selenium::WebDriver::Error::WebDriverError with "You must enable downloads in order to work with downloadable files.", got #<Selenium::WebDriver::Error::UnknownError: Cannot find downloads file system for session id: fb43ba6... os.arch: 'amd64', os.version: '10.0', java.version: '17.0.11'
1033:  Driver info: driver.version: unknown> with backtrace:
1034:  # ./rb/lib/selenium/webdriver/remote/response.rb:63:in `add_cause'
1035:  # ./rb/lib/selenium/webdriver/remote/response.rb:41:in `error'
...

1041:  # ./rb/lib/selenium/webdriver/remote/http/common.rb:67:in `call'
1042:  # ./rb/lib/selenium/webdriver/remote/bridge.rb:685:in `execute'
1043:  # ./rb/lib/selenium/webdriver/remote/features.rb:62:in `downloadable_files'
1044:  # ./rb/lib/selenium/webdriver/common/driver_extensions/has_file_downloads.rb:27:in `downloadable_files'
1045:  # ./rb/spec/integration/selenium/webdriver/remote/driver_spec.rb:87:in `block (3 levels) in <module:Remote>'
1046:  # ./rb/spec/integration/selenium/webdriver/remote/driver_spec.rb:86:in `block (2 levels) in <module:Remote>'
1047:  # ./rb/spec/integration/selenium/webdriver/remote/driver_spec.rb:86:in `block (2 levels) in <module:Remote>'
1048:  Finished in 22.11 seconds (files took 0.70913 seconds to load)
1049:  7 examples, 1 failure
1050:  Failed examples:
1051:  rspec ./rb/spec/integration/selenium/webdriver/remote/driver_spec.rb:84 # Selenium::WebDriver::Remote::Driver errors when not set
...

1063:  Selenium::WebDriver::Remote::Driver
1064:  exposes session_id
1065:  exposes remote status
1066:  Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead
1067:  uses a default file detector
1068:  lists downloads
1069:  downloads a file
1070:  deletes downloadable files
1071:  errors when not set (FAILED - 1)
1072:  Failures:
1073:  1) Selenium::WebDriver::Remote::Driver errors when not set
1074:  Failure/Error:
1075:  expect {
1076:  driver.downloadable_files
1077:  }.to raise_exception(Error::WebDriverError,
1078:  'You must enable downloads in order to work with downloadable files.')
1079:  expected Selenium::WebDriver::Error::WebDriverError with "You must enable downloads in order to work with downloadable files.", got #<Selenium::WebDriver::Error::UnknownError: Cannot find downloads file system for session id: 7190140... os.arch: 'amd64', os.version: '10.0', java.version: '17.0.11'
1080:  Driver info: driver.version: unknown> with backtrace:
1081:  # ./rb/lib/selenium/webdriver/remote/response.rb:63:in `add_cause'
1082:  # ./rb/lib/selenium/webdriver/remote/response.rb:41:in `error'
...

1088:  # ./rb/lib/selenium/webdriver/remote/http/common.rb:67:in `call'
1089:  # ./rb/lib/selenium/webdriver/remote/bridge.rb:685:in `execute'
1090:  # ./rb/lib/selenium/webdriver/remote/features.rb:62:in `downloadable_files'
1091:  # ./rb/lib/selenium/webdriver/common/driver_extensions/has_file_downloads.rb:27:in `downloadable_files'
1092:  # ./rb/spec/integration/selenium/webdriver/remote/driver_spec.rb:87:in `block (3 levels) in <module:Remote>'
1093:  # ./rb/spec/integration/selenium/webdriver/remote/driver_spec.rb:86:in `block (2 levels) in <module:Remote>'
1094:  # ./rb/spec/integration/selenium/webdriver/remote/driver_spec.rb:86:in `block (2 levels) in <module:Remote>'
1095:  Finished in 20.59 seconds (files took 0.89474 seconds to load)
1096:  7 examples, 1 failure
1097:  Failed examples:
1098:  rspec ./rb/spec/integration/selenium/webdriver/remote/driver_spec.rb:84 # Selenium::WebDriver::Remote::Driver errors when not set
...

1110:  Selenium::WebDriver::Remote::Driver
1111:  exposes session_id
1112:  exposes remote status
1113:  Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead
1114:  uses a default file detector
1115:  lists downloads
1116:  downloads a file
1117:  deletes downloadable files
1118:  errors when not set (FAILED - 1)
1119:  Failures:
1120:  1) Selenium::WebDriver::Remote::Driver errors when not set
1121:  Failure/Error:
1122:  expect {
1123:  driver.downloadable_files
1124:  }.to raise_exception(Error::WebDriverError,
1125:  'You must enable downloads in order to work with downloadable files.')
1126:  expected Selenium::WebDriver::Error::WebDriverError with "You must enable downloads in order to work with downloadable files.", got #<Selenium::WebDriver::Error::UnknownError: Cannot find downloads file system for session id: a509a4a... os.arch: 'amd64', os.version: '10.0', java.version: '17.0.11'
1127:  Driver info: driver.version: unknown> with backtrace:
1128:  # ./rb/lib/selenium/webdriver/remote/response.rb:63:in `add_cause'
1129:  # ./rb/lib/selenium/webdriver/remote/response.rb:41:in `error'
...

1135:  # ./rb/lib/selenium/webdriver/remote/http/common.rb:67:in `call'
1136:  # ./rb/lib/selenium/webdriver/remote/bridge.rb:685:in `execute'
1137:  # ./rb/lib/selenium/webdriver/remote/features.rb:62:in `downloadable_files'
1138:  # ./rb/lib/selenium/webdriver/common/driver_extensions/has_file_downloads.rb:27:in `downloadable_files'
1139:  # ./rb/spec/integration/selenium/webdriver/remote/driver_spec.rb:87:in `block (3 levels) in <module:Remote>'
1140:  # ./rb/spec/integration/selenium/webdriver/remote/driver_spec.rb:86:in `block (2 levels) in <module:Remote>'
1141:  # ./rb/spec/integration/selenium/webdriver/remote/driver_spec.rb:86:in `block (2 levels) in <module:Remote>'
1142:  Finished in 21.2 seconds (files took 0.66158 seconds to load)
1143:  7 examples, 1 failure
1144:  Failed examples:
1145:  rspec ./rb/spec/integration/selenium/webdriver/remote/driver_spec.rb:84 # Selenium::WebDriver::Remote::Driver errors when not set
1146:  ================================================================================
1147:  [32m[3,117 / 3,122][0m 19 / 29 tests, [31m[1m1 failed[0m;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver:zipper-edge-remote; 31s ... (4 actions, 1 running)
1148:  [32m[3,117 / 3,122][0m 19 / 29 tests, [31m[1m1 failed[0m;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver:zipper-edge-remote; 42s ... (4 actions, 1 running)
1149:  [32m[3,117 / 3,122][0m 19 / 29 tests, [31m[1m1 failed[0m;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver:zipper-edge-remote; 44s ... (4 actions, 1 running)
1150:  [32m[3,117 / 3,122][0m 19 / 29 tests, [31m[1m1 failed[0m;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver:storage-edge-remote; 47s ... (4 actions, 2 running)
1151:  [32m[3,118 / 3,122][0m 20 / 29 tests, [31m[1m1 failed[0m;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver:storage-edge-remote; 48s ... (4 actions, 1 running)
1152:  [32m[3,118 / 3,122][0m 20 / 29 tests, [31m[1m1 failed[0m;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver:storage-edge-remote; 58s ... (4 actions, 1 running)
1153:  [32m[3,118 / 3,122][0m 20 / 29 tests, [31m[1m1 failed[0m;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver:storage-edge-remote; 61s ... (4 actions, 1 running)
1154:  [32m[3,118 / 3,122][0m 20 / 29 tests, [31m[1m1 failed[0m;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver/remote:element-edge-remote; 34s ... (4 actions, 2 running)
1155:  [32m[3,119 / 3,122][0m 21 / 29 tests, [31m[1m1 failed[0m;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver/remote:element-edge-remote; 36s ... (3 actions, 1 running)
1156:  [32m[3,119 / 3,122][0m 21 / 29 tests, [31m[1m1 failed[0m;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver/remote:element-edge-remote; 46s ... (3 actions, 1 running)
1157:  [32m[3,119 / 3,122][0m 21 / 29 tests, [31m[1m1 failed[0m;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver:timeout-edge-remote; 18s ... (3 actions, 2 running)
1158:  [32m[3,120 / 3,122][0m 22 / 29 tests, [31m[1m1 failed[0m;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver:timeout-edge-remote; 20s ... (2 actions, 1 running)
1159:  [32m[3,120 / 3,122][0m 22 / 29 tests, [31m[1m1 failed[0m;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver:timeout-edge-remote; 30s ... (2 actions, 1 running)
1160:  [32m[3,120 / 3,122][0m 22 / 29 tests, [31m[1m1 failed[0m;[0m Testing //rb/spec/integration/selenium/webdriver/remote:element-edge-remote; 16s local, disk-cache ... (2 actions running)
1161:  [32m[3,121 / 3,122][0m 23 / 29 tests, [31m[1m1 failed[0m;[0m Testing //rb/spec/integration/selenium/webdriver:timeout-edge-remote; 1s local, disk-cache
1162:  [32m[3,121 / 3,122][0m 23 / 29 tests, [31m[1m1 failed[0m;[0m Testing //rb/spec/integration/selenium/webdriver:timeout-edge-remote; 11s local, disk-cache
1163:  [32m[3,121 / 3,122][0m 23 / 29 tests, [31m[1m1 failed[0m;[0m Testing //rb/spec/integration/selenium/webdriver:timeout-edge-remote; 23s local, disk-cache
1164:  [32m[3,122 / 3,123][0m 24 / 29 tests, [31m[1m1 failed[0m;[0m Testing //rb/spec/integration/selenium/webdriver/bidi:script-edge-remote; 0s disk-cache
1165:  [32m[3,122 / 3,123][0m 24 / 29 tests, [31m[1m1 failed[0m;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver/bidi:script-edge-remote
1166:  [32m[3,122 / 3,123][0m 24 / 29 tests, [31m[1m1 failed[0m;[0m Testing //rb/spec/integration/selenium/webdriver/bidi:script-edge-remote; 1s local, disk-cache
1167:  [32m[3,122 / 3,123][0m 24 / 29 tests, [31m[1m1 failed[0m;[0m Testing //rb/spec/integration/selenium/webdriver/bidi:script-edge-remote; 14s local, disk-cache
1168:  [32m[3,123 / 3,124][0m 25 / 29 tests, [31m[1m1 failed[0m;[0m Testing //rb/spec/integration/selenium/webdriver:devtools-edge-remote; 0s disk-cache
1169:  [32m[3,123 / 3,124][0m 25 / 29 tests, [31m[1m1 failed[0m;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver:devtools-edge-remote
1170:  [32m[3,123 / 3,124][0m 25 / 29 tests, [31m[1m1 failed[0m;[0m Testing //rb/spec/integration/selenium/webdriver:devtools-edge-remote; 1s local, disk-cache
1171:  [32m[3,123 / 3,124][0m 25 / 29 tests, [31m[1m1 failed[0m;[0m Testing //rb/spec/integration/selenium/webdriver:devtools-edge-remote; 88s local, disk-cache
1172:  [32m[3,124 / 3,125][0m 26 / 29 tests, [31m[1m1 failed[0m;[0m Testing //rb/spec/integration/selenium/webdriver/bidi:browsing_context-edge-remote; 0s disk-cache
1173:  [32m[3,124 / 3,125][0m 26 / 29 tests, [31m[1m1 failed[0m;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver/bidi:browsing_context-edge-remote
1174:  [32m[3,124 / 3,125][0m 26 / 29 tests, [31m[1m1 failed[0m;[0m Testing //rb/spec/integration/selenium/webdriver/bidi:browsing_context-edge-remote; 1s local, disk-cache
1175:  [32m[3,124 / 3,125][0m 26 / 29 tests, [31m[1m1 failed[0m;[0m Testing //rb/spec/integration/selenium/webdriver/bidi:browsing_context-edge-remote; 14s local, disk-cache
1176:  [32m[3,125 / 3,126][0m 27 / 29 tests, [31m[1m1 failed[0m;[0m Testing //rb/spec/integration/selenium/webdriver/bidi:log_inspector-edge-remote; 0s disk-cache
1177:  [32m[3,125 / 3,126][0m 27 / 29 tests, [31m[1m1 failed[0m;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver/bidi:log_inspector-edge-remote
1178:  [32m[3,125 / 3,126][0m 27 / 29 tests, [31m[1m1 failed[0m;[0m Testing //rb/spec/integration/selenium/webdriver/bidi:log_inspector-edge-remote; 1s local, disk-cache
1179:  [32m[3,125 / 3,126][0m 27 / 29 tests, [31m[1m1 failed[0m;[0m Testing //rb/spec/integration/selenium/webdriver/bidi:log_inspector-edge-remote; 14s local, disk-cache
1180:  [32m[3,126 / 3,127][0m 28 / 29 tests, [31m[1m1 failed[0m;[0m Testing //rb/spec/integration/selenium/webdriver:bidi-edge-remote; 0s disk-cache
1181:  [32m[3,126 / 3,127][0m 28 / 29 tests, [31m[1m1 failed[0m;[0m [Sched] Testing //rb/spec/integration/selenium/webdriver:bidi-edge-remote
1182:  [32m[3,126 / 3,127][0m 28 / 29 tests, [31m[1m1 failed[0m;[0m Testing //rb/spec/integration/selenium/webdriver:bidi-edge-remote; 1s local, disk-cache
1183:  [32m[3,126 / 3,127][0m 28 / 29 tests, [31m[1m1 failed[0m;[0m Testing //rb/spec/integration/selenium/webdriver:bidi-edge-remote; 14s local, disk-cache
1184:  [32mINFO: [0mFound 29 test targets...
1185:  [32mINFO: [0mElapsed time: 1531.342s, Critical Path: 778.22s
1186:  [32mINFO: [0m2866 processes: 1438 disk cache hit, 1168 internal, 200 local, 60 worker.
1187:  [32mINFO: [0mBuild completed, 1 test FAILED, 2866 total actions
1188:  //rb/spec/integration/selenium/webdriver:action_builder-edge-remote      [0m[32mPASSED[0m in 28.4s
1189:  //rb/spec/integration/selenium/webdriver:bidi-edge-remote                [0m[32mPASSED[0m in 14.5s
1190:  //rb/spec/integration/selenium/webdriver:devtools-edge-remote            [0m[32mPASSED[0m in 88.2s
1191:  //rb/spec/integration/selenium/webdriver:driver-edge-remote              [0m[32mPASSED[0m in 34.2s
1192:  //rb/spec/integration/selenium/webdriver:element-edge-remote             [0m[32mPASSED[0m in 62.1s
1193:  //rb/spec/integration/selenium/webdriver:error-edge-remote               [0m[32mPASSED[0m in 14.8s
...

1208:  //rb/spec/integration/selenium/webdriver/bidi:browsing_context-edge-remote [0m[32mPASSED[0m in 14.4s
1209:  //rb/spec/integration/selenium/webdriver/bidi:log_inspector-edge-remote  [0m[32mPASSED[0m in 14.6s
1210:  //rb/spec/integration/selenium/webdriver/bidi:script-edge-remote         [0m[32mPASSED[0m in 14.5s
1211:  //rb/spec/integration/selenium/webdriver/edge:driver-edge-remote         [0m[32mPASSED[0m in 34.6s
1212:  //rb/spec/integration/selenium/webdriver/edge:options-edge-remote        [0m[32mPASSED[0m in 21.4s
1213:  //rb/spec/integration/selenium/webdriver/edge:profile-edge-remote        [0m[32mPASSED[0m in 15.0s
1214:  //rb/spec/integration/selenium/webdriver/edge:service-edge-remote        [0m[32mPASSED[0m in 21.8s
1215:  //rb/spec/integration/selenium/webdriver/remote:element-edge-remote      [0m[32mPASSED[0m in 16.1s
1216:  //rb/spec/integration/selenium/webdriver/remote:driver-edge-remote       [0m[31m[1mFAILED[0m in 3 out of 3 in 29.1s
1217:  Stats over 3 runs: max = 29.1s, min = 27.9s, avg = 28.4s, dev = 0.5s
1218:  D:/_bazel/execroot/_main/bazel-out/x64_windows-fastbuild/testlogs/rb/spec/integration/selenium/webdriver/remote/driver-edge-remote/test.log
1219:  D:/_bazel/execroot/_main/bazel-out/x64_windows-fastbuild/testlogs/rb/spec/integration/selenium/webdriver/remote/driver-edge-remote/test_attempts/attempt_1.log
1220:  D:/_bazel/execroot/_main/bazel-out/x64_windows-fastbuild/testlogs/rb/spec/integration/selenium/webdriver/remote/driver-edge-remote/test_attempts/attempt_2.log
1221:  Executed 29 out of 29 tests: 28 tests pass and [0m[31m[1m1 fails locally[0m.
1222:  There were tests whose specified size is too big. Use the --test_verbose_timeout_warnings command line option to see which ones these are.
1223:  [0m
1224:  ##[error]Process completed with exit code 1.

```
</details></td></tr></table>
<hr>

<details><summary><strong>✨ CI feedback usage guide:</strong></summary><hr>

The CI feedback tool (`/checks)` automatically triggers when a PR has a failed check.
The tool analyzes the failed checks and provides several feedbacks:
- Failed stage
- Failed test name
- Failure summary
- Relevant error logs
        
In addition to being automatically triggered, the tool can also be invoked manually by commenting on a PR:
```
/checks "https://github.com/{repo_name}/actions/runs/{run_number}/job/{job_number}"
```
where `{repo_name}` is the name of the repository, `{run_number}` is the run number of the failed check, and `{job_number}` is the job number of the failed check.

#### Configuration options
- `enable_auto_checks_feedback` - if set to true, the tool will automatically provide feedback when a check is failed. Default is true.
- `excluded_checks_list` - a list of checks to exclude from the feedback, for example: ["check1", "check2"]. Default is an empty list.
- `enable_help_text` - if set to true, the tool will provide a help message with the feedback. Default is true.
- `persistent_comment` - if set to true, the tool will overwrite a previous checks comment with the new feedback. Default is true.
- `final_update_message` - if `persistent_comment` is true and updating a previous checks message, the tool will also create a new message: "Persistent checks updated to latest commit". Default is true.

See more information about the `checks` tool in the [docs](https://pr-agent-docs.codium.ai/tools/ci_feedback/).

</details>

Thank you, @joerg1985 !

Fix patch:
diff --git a/java/src/org/openqa/selenium/grid/node/Node.java b/java/src/org/openqa/selenium/grid/node/Node.java
index 68767fa0a26b9..98322648d201a 100644
--- a/java/src/org/openqa/selenium/grid/node/Node.java
+++ b/java/src/org/openqa/selenium/grid/node/Node.java
@@ -101,6 +101,12 @@
  * by {@code sessionId}. This returns a boolean.</td>
  * </tr>
  * <tr>
+ * <td>DELETE</td>
+ * <td>/se/grid/node/connection/{sessionId}</td>
+ * <td>Notifies the node about closure of a websocket connection for the {@link Session}
+ * identified by {@code sessionId}.</td>
+ * </tr>
+ * <tr>
  * <td>POST</td>
  * <td>/se/grid/node/connection/{sessionId}</td>
  * <td>Allows the node to be ask about whether or not new websocket connections are allowed for the {@link Session}
@@ -173,6 +179,9 @@ protected Node(
             get("/se/grid/node/owner/{sessionId}")
                 .to(params -> new IsSessionOwner(this, sessionIdFrom(params)))
                 .with(spanDecorator("node.is_session_owner").andThen(requiresSecret)),
+            delete("/se/grid/node/connection/{sessionId}")
+                .to(params -> new ReleaseConnection(this, sessionIdFrom(params)))
+                .with(spanDecorator("node.is_session_owner").andThen(requiresSecret)),
             post("/se/grid/node/connection/{sessionId}")
                 .to(params -> new TryAcquireConnection(this, sessionIdFrom(params)))
                 .with(spanDecorator("node.is_session_owner").andThen(requiresSecret)),
@@ -250,6 +259,8 @@ public TemporaryFilesystem getDownloadsFilesystem(UUID uuid) throws IOException
 
   public abstract boolean tryAcquireConnection(SessionId id);
 
+  public abstract void releaseConnection(SessionId id);
+
   public abstract boolean isSupporting(Capabilities capabilities);
 
   public abstract NodeStatus getStatus();
diff --git a/java/src/org/openqa/selenium/grid/node/ProxyNodeWebsockets.java b/java/src/org/openqa/selenium/grid/node/ProxyNodeWebsockets.java
index eff13dc5a40f5..e9583ca8fa0e8 100644
--- a/java/src/org/openqa/selenium/grid/node/ProxyNodeWebsockets.java
+++ b/java/src/org/openqa/selenium/grid/node/ProxyNodeWebsockets.java
@@ -237,7 +237,7 @@ private Consumer<Message> createWsEndPoint(
       WebSocket upstream =
           client.openSocket(
               new HttpRequest(GET, uri.toString()),
-              new ForwardingListener(downstream, sessionConsumer, sessionId));
+              new ForwardingListener(node, downstream, sessionConsumer, sessionId));
 
       return (msg) -> {
         try {
@@ -260,12 +260,17 @@ private Consumer<Message> createWsEndPoint(
   }
 
   private static class ForwardingListener implements WebSocket.Listener {
+    private final Node node;
     private final Consumer<Message> downstream;
     private final Consumer<SessionId> sessionConsumer;
     private final SessionId sessionId;
 
     public ForwardingListener(
-        Consumer<Message> downstream, Consumer<SessionId> sessionConsumer, SessionId sessionId) {
+        Node node,
+        Consumer<Message> downstream,
+        Consumer<SessionId> sessionConsumer,
+        SessionId sessionId) {
+      this.node = node;
       this.downstream = Objects.requireNonNull(downstream);
       this.sessionConsumer = Objects.requireNonNull(sessionConsumer);
       this.sessionId = Objects.requireNonNull(sessionId);
@@ -280,7 +285,7 @@ public void onBinary(byte[] data) {
     @Override
     public void onClose(int code, String reason) {
       downstream.accept(new CloseMessage(code, reason));
-      sessionConsumer.accept(sessionId);
+      node.releaseConnection(sessionId);
     }
 
     @Override
diff --git a/java/src/org/openqa/selenium/grid/node/ReleaseConnection.java b/java/src/org/openqa/selenium/grid/node/ReleaseConnection.java
new file mode 100644
index 0000000000000..a9a7e85b0f7aa
--- /dev/null
+++ b/java/src/org/openqa/selenium/grid/node/ReleaseConnection.java
@@ -0,0 +1,43 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The SFC 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.openqa.selenium.grid.node;
+
+import java.io.UncheckedIOException;
+import org.openqa.selenium.internal.Require;
+import org.openqa.selenium.remote.SessionId;
+import org.openqa.selenium.remote.http.HttpHandler;
+import org.openqa.selenium.remote.http.HttpRequest;
+import org.openqa.selenium.remote.http.HttpResponse;
+
+class ReleaseConnection implements HttpHandler {
+
+  private final Node node;
+  private final SessionId id;
+
+  ReleaseConnection(Node node, SessionId id) {
+    this.node = Require.nonNull("Node", node);
+    this.id = Require.nonNull("Session id", id);
+  }
+
+  @Override
+  public HttpResponse execute(HttpRequest req) throws UncheckedIOException {
+    node.releaseConnection(id);
+
+    return new HttpResponse().setStatus(200);
+  }
+}
diff --git a/java/src/org/openqa/selenium/grid/node/TryAcquireConnection.java b/java/src/org/openqa/selenium/grid/node/TryAcquireConnection.java
index 6c8822bea84cd..076e5f7d32037 100644
--- a/java/src/org/openqa/selenium/grid/node/TryAcquireConnection.java
+++ b/java/src/org/openqa/selenium/grid/node/TryAcquireConnection.java
@@ -19,8 +19,8 @@
 
 import static org.openqa.selenium.remote.http.Contents.asJson;
 
-import com.google.common.collect.ImmutableMap;
 import java.io.UncheckedIOException;
+import java.util.Map;
 import org.openqa.selenium.internal.Require;
 import org.openqa.selenium.remote.SessionId;
 import org.openqa.selenium.remote.http.HttpHandler;
@@ -39,7 +39,6 @@ class TryAcquireConnection implements HttpHandler {
 
   @Override
   public HttpResponse execute(HttpRequest req) throws UncheckedIOException {
-    return new HttpResponse()
-        .setContent(asJson(ImmutableMap.of("value", node.tryAcquireConnection(id))));
+    return new HttpResponse().setContent(asJson(Map.of("value", node.tryAcquireConnection(id))));
   }
 }
diff --git a/java/src/org/openqa/selenium/grid/node/config/NodeFlags.java b/java/src/org/openqa/selenium/grid/node/config/NodeFlags.java
index b56e57b3dcb97..e090fcd928b7a 100644
--- a/java/src/org/openqa/selenium/grid/node/config/NodeFlags.java
+++ b/java/src/org/openqa/selenium/grid/node/config/NodeFlags.java
@@ -81,8 +81,8 @@ public class NodeFlags implements HasRoles {
   @Parameter(
       names = {"--connection-limit-per-session"},
       description =
-          "Let X be the maximum number of websocket connections per session.This will ensure one"
-              + " session is not able to exhaust the connection limit of the host")
+          "Let X be the maximum number of concurrent websocket connections per session. This will"
+              + " ensure one session is not able to exhaust the connection limit of the host")
   @ConfigValue(section = NODE_SECTION, name = "connection-limit-per-session", example = "8")
   public int connectionLimitPerSession = DEFAULT_CONNECTION_LIMIT;
 
diff --git a/java/src/org/openqa/selenium/grid/node/k8s/OneShotNode.java b/java/src/org/openqa/selenium/grid/node/k8s/OneShotNode.java
index af8c05cf7a7c1..45887df7da2e2 100644
--- a/java/src/org/openqa/selenium/grid/node/k8s/OneShotNode.java
+++ b/java/src/org/openqa/selenium/grid/node/k8s/OneShotNode.java
@@ -365,7 +365,24 @@ public boolean isSessionOwner(SessionId id) {
 
   @Override
   public boolean tryAcquireConnection(SessionId id) {
-    return sessionId.equals(id) && connectionLimitPerSession > connectionCounter.getAndIncrement();
+    if (!sessionId.equals(id)) {
+      return false;
+    }
+
+    if (connectionLimitPerSession > connectionCounter.getAndIncrement()) {
+      return true;
+    }
+
+    // ensure a rejected connection will not be counted
+    connectionCounter.getAndDecrement();
+    return false;
+  }
+
+  @Override
+  public void releaseConnection(SessionId id) {
+    if (sessionId.equals(id)) {
+      connectionCounter.getAndDecrement();
+    }
   }
 
   @Override
diff --git a/java/src/org/openqa/selenium/grid/node/local/LocalNode.java b/java/src/org/openqa/selenium/grid/node/local/LocalNode.java
index bb45cd00579dc..665fce7ac567a 100644
--- a/java/src/org/openqa/selenium/grid/node/local/LocalNode.java
+++ b/java/src/org/openqa/selenium/grid/node/local/LocalNode.java
@@ -623,7 +623,31 @@ public boolean tryAcquireConnection(SessionId id) throws NoSuchSessionException
 
     AtomicLong counter = slot.getConnectionCounter();
 
-    return connectionLimitPerSession > counter.getAndIncrement();
+    if (connectionLimitPerSession > counter.getAndIncrement()) {
+      return true;
+    }
+
+    // ensure a rejected connection will not be counted
+    counter.getAndDecrement();
+    return false;
+  }
+
+  @Override
+  public void releaseConnection(SessionId id) {
+    SessionSlot slot = currentSessions.getIfPresent(id);
+
+    if (slot == null) {
+      return;
+    }
+
+    if (connectionLimitPerSession == -1) {
+      // no limit
+      return;
+    }
+
+    AtomicLong counter = slot.getConnectionCounter();
+
+    counter.decrementAndGet();
   }
 
   @Override
diff --git a/java/src/org/openqa/selenium/grid/node/remote/RemoteNode.java b/java/src/org/openqa/selenium/grid/node/remote/RemoteNode.java
index d83d7bbbdd9ca..a40edb20afde3 100644
--- a/java/src/org/openqa/selenium/grid/node/remote/RemoteNode.java
+++ b/java/src/org/openqa/selenium/grid/node/remote/RemoteNode.java
@@ -196,6 +196,18 @@ public boolean tryAcquireConnection(SessionId id) {
     return Boolean.TRUE.equals(Values.get(res, Boolean.class));
   }
 
+  @Override
+  public void releaseConnection(SessionId id) {
+    Require.nonNull("Session ID", id);
+
+    HttpRequest req = new HttpRequest(DELETE, "/se/grid/node/connection/" + id);
+    HttpTracing.inject(tracer, tracer.getCurrentContext(), req);
+
+    HttpResponse res = client.with(addSecret).execute(req);
+
+    Values.get(res, Void.class);
+  }
+
   @Override
   public Session getSession(SessionId id) throws NoSuchSessionException {
     Require.nonNull("Session ID", id);


Test patch:
diff --git a/java/test/org/openqa/selenium/grid/distributor/AddingNodesTest.java b/java/test/org/openqa/selenium/grid/distributor/AddingNodesTest.java
index 1485d04fca4c6..12647fe9c38b2 100644
--- a/java/test/org/openqa/selenium/grid/distributor/AddingNodesTest.java
+++ b/java/test/org/openqa/selenium/grid/distributor/AddingNodesTest.java
@@ -450,6 +450,9 @@ public boolean tryAcquireConnection(SessionId id) {
       return false;
     }
 
+    @Override
+    public void releaseConnection(SessionId id) {}
+
     @Override
     public boolean isSupporting(Capabilities capabilities) {
       return Objects.equals("cake", capabilities.getCapability("cheese"));
diff --git a/java/test/org/openqa/selenium/grid/router/DistributedTest.java b/java/test/org/openqa/selenium/grid/router/DistributedTest.java
index 821711b2c5be6..46eb746d48b77 100644
--- a/java/test/org/openqa/selenium/grid/router/DistributedTest.java
+++ b/java/test/org/openqa/selenium/grid/router/DistributedTest.java
@@ -30,8 +30,12 @@
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.openqa.selenium.Capabilities;
+import org.openqa.selenium.HasCapabilities;
 import org.openqa.selenium.SessionNotCreatedException;
 import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.bidi.BiDi;
+import org.openqa.selenium.bidi.BiDiProvider;
 import org.openqa.selenium.grid.config.MapConfig;
 import org.openqa.selenium.grid.config.MemoizedConfig;
 import org.openqa.selenium.grid.config.TomlConfig;
@@ -43,6 +47,7 @@
 import org.openqa.selenium.netty.server.NettyServer;
 import org.openqa.selenium.remote.RemoteWebDriver;
 import org.openqa.selenium.remote.http.ClientConfig;
+import org.openqa.selenium.remote.http.ConnectionFailedException;
 import org.openqa.selenium.remote.http.Contents;
 import org.openqa.selenium.remote.http.HttpClient;
 import org.openqa.selenium.remote.http.HttpMethod;
@@ -76,7 +81,9 @@ public void setupServers() {
                         + "\n"
                         + "override-max-sessions = true"
                         + "\n"
-                        + "max-sessions = 2")));
+                        + "max-sessions = 2"
+                        + "\n"
+                        + "connection-limit-per-session = 3")));
     tearDowns.add(deployment);
 
     server = deployment.getServer();
@@ -192,4 +199,46 @@ void clientTimeoutDoesNotLeakARunningBrowser() throws Exception {
       Safely.safelyCall(healthy::quit);
     }
   }
+
+  @Test
+  void connectionLimitIsRespected() throws Exception {
+    assertThat(server.isStarted()).isTrue();
+
+    // don't use the RemoteWebDriver.builder here, using it does create an unknown number of
+    // connections
+    WebDriver driver = new RemoteWebDriver(server.getUrl(), browser.getCapabilities());
+
+    try {
+      Capabilities caps = ((HasCapabilities) driver).getCapabilities();
+      BiDiProvider biDiProvider = new BiDiProvider();
+
+      BiDi cnn1 = biDiProvider.getImplementation(caps, null).getBiDi();
+      BiDi cnn2 = biDiProvider.getImplementation(caps, null).getBiDi();
+      BiDi cnn3 = biDiProvider.getImplementation(caps, null).getBiDi();
+
+      Assertions.assertThrows(
+          ConnectionFailedException.class,
+          () -> biDiProvider.getImplementation(caps, null).getBiDi());
+      cnn1.close();
+      BiDi cnn4 = biDiProvider.getImplementation(caps, null).getBiDi();
+
+      Assertions.assertThrows(
+          ConnectionFailedException.class,
+          () -> biDiProvider.getImplementation(caps, null).getBiDi());
+      cnn2.close();
+      cnn3.close();
+      BiDi cnn5 = biDiProvider.getImplementation(caps, null).getBiDi();
+      BiDi cnn6 = biDiProvider.getImplementation(caps, null).getBiDi();
+
+      Assertions.assertThrows(
+          ConnectionFailedException.class,
+          () -> biDiProvider.getImplementation(caps, null).getBiDi());
+
+      cnn4.close();
+      cnn5.close();
+      cnn6.close();
+    } finally {
+      Safely.safelyCall(driver::quit);
+    }
+  }
 }