Skip to content

Commit a522fb7

Browse files
pujaganidiemol
andauthored
[grid] Make distributor pick up session requests from the queue only if the grid is not full (#8940)
* [grid] Make distributor pick up session requests from the queue only if the grid is not full * [grid] Move the handleSession method into the runnable * [grid] Move private methods to the inner class NewSessionRunnable. * Code formatting and fixing test * Adding a tear down for NewSessionCreationTest * Moving `exerciseDriver` back to EndToEndTest Co-authored-by: Diego Molina <[email protected]> Co-authored-by: Diego Molina <[email protected]>
1 parent 988eb75 commit a522fb7

File tree

4 files changed

+118
-101
lines changed

4 files changed

+118
-101
lines changed

java/server/src/org/openqa/selenium/grid/distributor/local/LocalDistributor.java

+82-41
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,12 @@
7979
import java.util.List;
8080
import java.util.Map;
8181
import java.util.Optional;
82+
import java.util.Queue;
8283
import java.util.Set;
84+
import java.util.concurrent.ConcurrentLinkedQueue;
85+
import java.util.concurrent.Executors;
86+
import java.util.concurrent.ScheduledExecutorService;
87+
import java.util.concurrent.TimeUnit;
8388
import java.util.concurrent.locks.Lock;
8489
import java.util.concurrent.locks.ReadWriteLock;
8590
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -98,6 +103,9 @@ public class LocalDistributor extends Distributor {
98103
private final Secret registrationSecret;
99104
private final Regularly hostChecker = new Regularly("distributor host checker");
100105
private final Map<NodeId, Runnable> allChecks = new HashMap<>();
106+
private final Queue<RequestId> requestIds = new ConcurrentLinkedQueue<>();
107+
private final ScheduledExecutorService executorService =
108+
Executors.newSingleThreadScheduledExecutor();
101109

102110
private final ReadWriteLock lock = new ReentrantReadWriteLock(/* fair */ true);
103111
private final GridModel model;
@@ -125,64 +133,92 @@ public LocalDistributor(
125133
bus.addListener(NodeStatusEvent.listener(this::register));
126134
bus.addListener(NodeStatusEvent.listener(model::refresh));
127135
bus.addListener(NodeDrainComplete.listener(this::remove));
136+
bus.addListener(NewSessionRequestEvent.listener(requestIds::offer));
128137

129-
bus.addListener(NewSessionRequestEvent.listener(reqId -> {
130-
Optional<HttpRequest> sessionRequest = this.sessionRequests.remove(reqId);
131-
// Check if polling the queue did not return null
132-
if (sessionRequest.isPresent()) {
133-
handleNewSessionRequest(sessionRequest.get(), reqId);
134-
} else {
135-
fireSessionRejectedEvent(
136-
"Unable to poll request from the new session request queue.",
137-
reqId);
138-
}
139-
}));
138+
Thread shutdownHook = new Thread(this::callExecutorShutdown);
139+
Runtime.getRuntime().addShutdownHook(shutdownHook);
140+
NewSessionRunnable runnable = new NewSessionRunnable();
141+
executorService.scheduleAtFixedRate(runnable, 0, 1000, TimeUnit.MILLISECONDS);
140142
}
141143

142-
private void handleNewSessionRequest(HttpRequest sessionRequest, RequestId reqId) {
143-
144-
try (Span span = newSpanAsChildOf(tracer, sessionRequest, "distributor.poll_queue")) {
145-
Map<String, EventAttributeValue> attributeMap = new HashMap<>();
146-
attributeMap.put(
147-
AttributeKey.LOGGER_CLASS.getKey(), EventAttribute.setValue(getClass().getName()));
148-
span.setAttribute(AttributeKey.REQUEST_ID.getKey(), reqId.toString());
149-
attributeMap.put(AttributeKey.REQUEST_ID.getKey(), EventAttribute.setValue(reqId.toString()));
144+
public class NewSessionRunnable implements Runnable {
145+
@Override
146+
public void run() {
147+
Lock writeLock = lock.writeLock();
148+
writeLock.lock();
149+
try {
150+
if (!requestIds.isEmpty()) {
151+
boolean hasCapacity = nodes
152+
.keySet()
153+
.stream()
154+
.anyMatch(key -> nodes.get(key).getStatus().hasCapacity());
155+
if (hasCapacity) {
156+
RequestId reqId = requestIds.poll();
157+
if (reqId != null) {
158+
Optional<HttpRequest> optionalHttpRequest = sessionRequests.remove(reqId);
159+
// Check if polling the queue did not return null
160+
if (optionalHttpRequest.isPresent()) {
161+
handleNewSessionRequest(optionalHttpRequest.get(), reqId);
162+
} else {
163+
fireSessionRejectedEvent(
164+
"Unable to poll request from the new session request queue.",
165+
reqId);
166+
}
167+
}
168+
}
169+
}
170+
} finally {
171+
writeLock.unlock();
172+
}
173+
}
150174

151-
attributeMap.put("request", EventAttribute.setValue(sessionRequest.toString()));
152-
Either<SessionNotCreatedException, CreateSessionResponse> response =
175+
private void handleNewSessionRequest(HttpRequest sessionRequest, RequestId reqId) {
176+
try (Span span = newSpanAsChildOf(tracer, sessionRequest, "distributor.poll_queue")) {
177+
Map<String, EventAttributeValue> attributeMap = new HashMap<>();
178+
attributeMap.put(
179+
AttributeKey.LOGGER_CLASS.getKey(),
180+
EventAttribute.setValue(getClass().getName()));
181+
span.setAttribute(AttributeKey.REQUEST_ID.getKey(), reqId.toString());
182+
attributeMap.put(
183+
AttributeKey.REQUEST_ID.getKey(),
184+
EventAttribute.setValue(reqId.toString()));
185+
186+
attributeMap.put("request", EventAttribute.setValue(sessionRequest.toString()));
187+
Either<SessionNotCreatedException, CreateSessionResponse> response =
153188
newSession(sessionRequest);
154-
if (response.isRight()) {
155-
CreateSessionResponse sessionResponse = response.right();
156-
NewSessionResponse newSessionResponse =
189+
if (response.isRight()) {
190+
CreateSessionResponse sessionResponse = response.right();
191+
NewSessionResponse newSessionResponse =
157192
new NewSessionResponse(
158-
reqId,
159-
sessionResponse.getSession(),
160-
sessionResponse.getDownstreamEncodedResponse());
193+
reqId,
194+
sessionResponse.getSession(),
195+
sessionResponse.getDownstreamEncodedResponse());
161196

162-
bus.fire(new NewSessionResponseEvent(newSessionResponse));
163-
} else {
164-
SessionNotCreatedException exception = response.left();
197+
bus.fire(new NewSessionResponseEvent(newSessionResponse));
198+
} else {
199+
SessionNotCreatedException exception = response.left();
165200

166-
if (exception instanceof RetrySessionRequestException) {
167-
boolean retried = this.sessionRequests.retryAddToQueue(sessionRequest, reqId);
201+
if (exception instanceof RetrySessionRequestException) {
202+
boolean retried = sessionRequests.retryAddToQueue(sessionRequest, reqId);
168203

169-
attributeMap.put("request.retry_add", EventAttribute.setValue(retried));
170-
span.addEvent("Retry adding to front of queue. All slots are busy.", attributeMap);
204+
attributeMap.put("request.retry_add", EventAttribute.setValue(retried));
205+
span.addEvent("Retry adding to front of queue. No slot available.", attributeMap);
171206

172-
if (!retried) {
173-
span.addEvent("Retry adding to front of queue failed.", attributeMap);
207+
if (!retried) {
208+
span.addEvent("Retry adding to front of queue failed.", attributeMap);
209+
fireSessionRejectedEvent(exception.getMessage(), reqId);
210+
}
211+
} else {
174212
fireSessionRejectedEvent(exception.getMessage(), reqId);
175213
}
176-
} else {
177-
fireSessionRejectedEvent(exception.getMessage(), reqId);
178214
}
179215
}
180216
}
181-
}
182217

183-
private void fireSessionRejectedEvent(String message, RequestId reqId) {
184-
bus.fire(
218+
private void fireSessionRejectedEvent(String message, RequestId reqId) {
219+
bus.fire(
185220
new NewSessionRejectedEvent(new NewSessionErrorResponse(reqId, message)));
221+
}
186222
}
187223

188224
public static Distributor create(Config config) {
@@ -393,4 +429,9 @@ protected Supplier<CreateSessionResponse> reserve(SlotId slotId, CreateSessionRe
393429
writeLock.unlock();
394430
}
395431
}
432+
433+
public void callExecutorShutdown() {
434+
LOG.info("Shutting down Distributor executor service");
435+
executorService.shutdownNow();
436+
}
396437
}

java/server/test/org/openqa/selenium/grid/router/EndToEndTest.java

+12-17
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ private static TestData createStandalone() {
141141
"port = " + PortProber.findFreePort(),
142142
"registration-secret = \"provolone\"",
143143
"[sessionqueue]",
144-
"session-request-timeout = 2",
144+
"session-request-timeout = 10",
145145
"session-retry-interval = 1"
146146
};
147147
Config config = new MemoizedConfig(
@@ -181,7 +181,7 @@ private static TestData createHubAndNode() {
181181
"[server]",
182182
"registration-secret = \"feta\"",
183183
"[sessionqueue]",
184-
"session-request-timeout = 2",
184+
"session-request-timeout = 10",
185185
"session-retry-interval = 1"
186186
};
187187

@@ -228,7 +228,7 @@ private static TestData createFullyDistributed() {
228228
"[server]",
229229
"registration-secret = \"colby\"",
230230
"[sessionqueue]",
231-
"session-request-timeout = 2",
231+
"session-request-timeout = 10",
232232
"session-retry-interval = 1"
233233
};
234234

@@ -310,26 +310,27 @@ private static TestData createFullyDistributed() {
310310
eventServer::stop);
311311
}
312312

313-
private static void waitUntilReady(Server<?> server, Duration duration) {
314-
waitUntilReady(server, duration, false);
315-
}
316-
317-
private static void waitUntilReady(Server<?> server, Duration duration, boolean printOutput) {
313+
private static void waitUntilReady(Server<?> server, Duration duration, boolean logOutput) {
318314
HttpClient client = HttpClient.Factory.createDefault().createClient(server.getUrl());
319315

320316
new FluentWait<>(client)
321317
.withTimeout(duration)
322318
.pollingEvery(Duration.ofSeconds(1))
323319
.until(c -> {
324320
HttpResponse response = c.execute(new HttpRequest(GET, "/status"));
325-
if (printOutput) {
321+
if (logOutput) {
326322
System.out.println(Contents.string(response));
327323
}
328324
Map<String, Object> status = Values.get(response, MAP_TYPE);
329-
return Boolean.TRUE.equals(status.get("ready"));
325+
return Boolean.TRUE.equals(status != null &&
326+
Boolean.parseBoolean(status.get("ready").toString()));
330327
});
331328
}
332329

330+
private static void waitUntilReady(Server<?> server, Duration duration) {
331+
waitUntilReady(server, duration, false);
332+
}
333+
333334
private static Config setRandomPort(Config config) {
334335
return new MemoizedConfig(
335336
new CompoundConfig(
@@ -397,7 +398,7 @@ public void exerciseDriver() {
397398
// Kill the session, and wait until the grid says it's ready
398399
driver.quit();
399400

400-
waitUntilReady(server, Duration.ofSeconds(200), true);
401+
waitUntilReady(server, Duration.ofSeconds(100), true);
401402

402403
// And now we're good to go.
403404
driver = new RemoteWebDriver(server.getUrl(), caps);
@@ -433,10 +434,6 @@ public void shouldAllowPassthroughForW3CMode() {
433434

434435
@Test
435436
public void shouldRejectSessionRequestIfCapsNotSupported() {
436-
Capabilities caps = new ImmutableCapabilities("browserName", "cheese", "type", "cheddar");
437-
WebDriver driver = new RemoteWebDriver(server.getUrl(), caps);
438-
driver.get("http://www.google.com");
439-
440437
try {
441438
Capabilities unsupportedCaps = new ImmutableCapabilities("browserName", "brie");
442439
WebDriver disposable = new RemoteWebDriver(server.getUrl(), unsupportedCaps);
@@ -445,8 +442,6 @@ public void shouldRejectSessionRequestIfCapsNotSupported() {
445442
} catch (SessionNotCreatedException expected) {
446443
// Fall through
447444
}
448-
449-
driver.quit();
450445
}
451446

452447
@Test

java/server/test/org/openqa/selenium/grid/router/NewSessionCreationTest.java

+11-21
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@
2020
import com.google.common.collect.ImmutableList;
2121
import com.google.common.collect.ImmutableMap;
2222
import com.google.common.collect.ImmutableSet;
23+
24+
import org.junit.After;
2325
import org.junit.Before;
2426
import org.junit.Test;
2527
import org.openqa.selenium.Capabilities;
2628
import org.openqa.selenium.ImmutableCapabilities;
2729
import org.openqa.selenium.SessionNotCreatedException;
28-
import org.openqa.selenium.WebDriverInfo;
2930
import org.openqa.selenium.chrome.ChromeDriverInfo;
3031
import org.openqa.selenium.events.EventBus;
3132
import org.openqa.selenium.events.local.GuavaEventBus;
@@ -35,7 +36,6 @@
3536
import org.openqa.selenium.grid.distributor.Distributor;
3637
import org.openqa.selenium.grid.distributor.local.LocalDistributor;
3738
import org.openqa.selenium.grid.node.Node;
38-
import org.openqa.selenium.grid.node.config.DriverServiceSessionFactory;
3939
import org.openqa.selenium.grid.node.local.LocalNode;
4040
import org.openqa.selenium.grid.security.Secret;
4141
import org.openqa.selenium.grid.server.BaseServerOptions;
@@ -55,12 +55,10 @@
5555
import org.openqa.selenium.remote.http.HttpRequest;
5656
import org.openqa.selenium.remote.http.HttpResponse;
5757
import org.openqa.selenium.remote.http.Routable;
58-
import org.openqa.selenium.remote.service.DriverService;
5958
import org.openqa.selenium.remote.tracing.DefaultTestTracer;
6059
import org.openqa.selenium.remote.tracing.Tracer;
6160
import org.openqa.selenium.testing.drivers.Browser;
6261

63-
import java.net.MalformedURLException;
6462
import java.net.URI;
6563
import java.net.URISyntaxException;
6664
import java.time.Duration;
@@ -81,6 +79,7 @@ public class NewSessionCreationTest {
8179
private EventBus events;
8280
private HttpClient.Factory clientFactory;
8381
private Secret registrationSecret;
82+
private Server<?> server;
8483

8584
@Before
8685
public void setup() {
@@ -90,6 +89,11 @@ public void setup() {
9089
registrationSecret = new Secret("hereford hop");
9190
}
9291

92+
@After
93+
public void stopServer() {
94+
server.stop();
95+
}
96+
9397
@Test
9498
public void ensureJsCannotCreateANewSession() throws URISyntaxException {
9599
ChromeDriverInfo chromeDriverInfo = new ChromeDriverInfo();
@@ -116,7 +120,7 @@ public void ensureJsCannotCreateANewSession() throws URISyntaxException {
116120
Routable router = new Router(tracer, clientFactory, sessions, queuer, distributor)
117121
.with(new EnsureSpecCompliantHeaders(ImmutableList.of(), ImmutableSet.of()));
118122

119-
Server<?> server = new NettyServer(
123+
server = new NettyServer(
120124
new BaseServerOptions(new MapConfig(ImmutableMap.of())),
121125
router,
122126
new ProxyCdpIntoGrid(clientFactory, sessions))
@@ -167,8 +171,7 @@ public void ensureJsCannotCreateANewSession() throws URISyntaxException {
167171
}
168172

169173
@Test
170-
public void shouldRetryNewSessionRequestOnUnexpectedError() throws
171-
URISyntaxException, MalformedURLException {
174+
public void shouldRetryNewSessionRequestOnUnexpectedError() throws URISyntaxException {
172175
Capabilities capabilities = new ImmutableCapabilities("browserName", "cheese");
173176
URI nodeUri = new URI("http://localhost:4444");
174177
CombinedHandler handler = new CombinedHandler();
@@ -218,7 +221,7 @@ public void shouldRetryNewSessionRequestOnUnexpectedError() throws
218221
Router router = new Router(tracer, clientFactory, sessions, queuer, distributor);
219222
handler.addHandler(router);
220223

221-
Server<?> server = new NettyServer(
224+
server = new NettyServer(
222225
new BaseServerOptions(
223226
new MapConfig(ImmutableMap.of())),
224227
handler);
@@ -236,17 +239,4 @@ public void shouldRetryNewSessionRequestOnUnexpectedError() throws
236239
assertThat(httpResponse.getStatus()).isEqualTo(HTTP_OK);
237240
}
238241

239-
private LocalNode.Builder addDriverFactory(
240-
LocalNode.Builder builder,
241-
WebDriverInfo info,
242-
DriverService.Builder<?, ?> driverService) {
243-
return builder.add(
244-
info.getCanonicalCapabilities(),
245-
new DriverServiceSessionFactory(
246-
tracer,
247-
clientFactory,
248-
info.getCanonicalCapabilities(),
249-
info::isSupporting,
250-
driverService));
251-
}
252242
}

0 commit comments

Comments
 (0)