Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions java/src/org/openqa/selenium/grid/data/SessionClosedData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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.data;

import java.util.Map;
import org.openqa.selenium.internal.Require;
import org.openqa.selenium.json.JsonException;
import org.openqa.selenium.remote.SessionId;

/**
* Data structure that carries both SessionId and closure reason for SessionClosedEvent. Keeps
* closure state separate from the core SessionId class to avoid breaking changes.
*/
public class SessionClosedData {
private final SessionId sessionId;
private final SessionClosedReason reason;

public SessionClosedData(SessionId sessionId, SessionClosedReason reason) {
this.sessionId = Require.nonNull("Session ID", sessionId);
this.reason = Require.nonNull("Reason", reason);
}

public SessionId getSessionId() {
return sessionId;
}

public SessionClosedReason getReason() {
return reason;
}

@Override
public String toString() {
return String.format("SessionClosedData{sessionId=%s, reason=%s}", sessionId, reason);
}

private Object toJson() {
return Map.of("sessionId", sessionId, "reason", reason);
}

private static SessionClosedData fromJson(Object raw) {
if (raw instanceof Map) {
Map<?, ?> map = (Map<?, ?>) raw;
Object sessionIdObj = map.get("sessionId");
Object reasonObj = map.get("reason");

if (sessionIdObj instanceof String) {
SessionId sessionId = new SessionId((String) sessionIdObj);
SessionClosedReason reason = SessionClosedReason.valueOf((String) reasonObj);
return new SessionClosedData(sessionId, reason);
}
}

throw new JsonException("Unable to coerce session closed data from " + raw);
}
}
22 changes: 19 additions & 3 deletions java/src/org/openqa/selenium/grid/data/SessionClosedEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,29 @@ public class SessionClosedEvent extends Event {

private static final EventName SESSION_CLOSED = new EventName("session-closed");

// Backward compatible constructor
public SessionClosedEvent(SessionId id) {
super(SESSION_CLOSED, id);
this(id, SessionClosedReason.QUIT_COMMAND);
}

public static EventListener<SessionId> listener(Consumer<SessionId> handler) {
public SessionClosedEvent(SessionId id, SessionClosedReason reason) {
super(SESSION_CLOSED, new SessionClosedData(id, reason));
Require.nonNull("Session ID", id);
Require.nonNull("Reason", reason);
}

// Standard listener method that provides access to both SessionId and reason
public static EventListener<SessionClosedData> listener(Consumer<SessionClosedData> handler) {
Require.nonNull("Handler", handler);

return new EventListener<>(SESSION_CLOSED, SessionClosedData.class, handler);
}

// Convenience method for listeners that only care about the SessionId
public static EventListener<SessionClosedData> sessionListener(Consumer<SessionId> handler) {
Require.nonNull("Handler", handler);

return new EventListener<>(SESSION_CLOSED, SessionId.class, handler);
return new EventListener<>(
SESSION_CLOSED, SessionClosedData.class, data -> handler.accept(data.getSessionId()));
}
}
39 changes: 39 additions & 0 deletions java/src/org/openqa/selenium/grid/data/SessionClosedReason.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// 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.data;

public enum SessionClosedReason {
/** Session was closed normally via QUIT command from client */
QUIT_COMMAND("session closed normally (QUIT command)"),
/** Session timed out due to inactivity */
TIMEOUT("session timed out due to inactivity"),
/** Node was removed from the grid */
NODE_REMOVED("node was removed from the grid"),
/** Node was restarted */
NODE_RESTARTED("node was restarted");

private final String reasonText;

SessionClosedReason(String reasonText) {
this.reasonText = reasonText;
}

public String getReasonText() {
return reasonText;
}
}
45 changes: 45 additions & 0 deletions java/src/org/openqa/selenium/grid/data/SessionRemovalInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// 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.data;

import java.net.URI;
import java.time.Duration;
import java.time.Instant;

public class SessionRemovalInfo {
private final Instant removedAt;
private final String reason;
private final URI nodeUri;

public SessionRemovalInfo(String reason, URI nodeUri) {
this.removedAt = Instant.now();
this.reason = reason;
this.nodeUri = nodeUri;
}

@Override
public String toString() {
Duration elapsed = Duration.between(removedAt, Instant.now());
long seconds = Math.max(elapsed.toSeconds(), 0);
String timeAgo = seconds == 1 ? "1 second ago" : seconds + " seconds ago";

return String.format(
"removed at %s (%s), reason: %s, node: %s",
removedAt, timeAgo, reason, nodeUri != null ? nodeUri : "unknown");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public LocalGridModel(EventBus events) {
this.events = Require.nonNull("Event bus", events);

this.events.addListener(NodeDrainStarted.listener(nodeId -> setAvailability(nodeId, DRAINING)));
this.events.addListener(SessionClosedEvent.listener(this::release));
this.events.addListener(SessionClosedEvent.sessionListener(this::release));
}

public static LocalGridModel create(Config config) {
Expand Down
10 changes: 8 additions & 2 deletions java/src/org/openqa/selenium/grid/node/local/LocalNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
import org.openqa.selenium.grid.data.NodeId;
import org.openqa.selenium.grid.data.NodeStatus;
import org.openqa.selenium.grid.data.Session;
import org.openqa.selenium.grid.data.SessionClosedReason;
import org.openqa.selenium.grid.data.Slot;
import org.openqa.selenium.grid.data.SlotId;
import org.openqa.selenium.grid.jmx.JMXHelper;
Expand Down Expand Up @@ -334,12 +335,17 @@ private void stopTimedOutSession(SessionId id, SessionSlot slot, RemovalCause ca
attributeMap.put("session.id", id.toString());
attributeMap.put("session.timeout_in_seconds", getSessionTimeout().toSeconds());
attributeMap.put("session.remove.cause", cause.name());

// Determine the SessionClosedReason based on RemovalCause
SessionClosedReason closeReason;
if (cause == RemovalCause.EXPIRED) {
closeReason = SessionClosedReason.TIMEOUT;
// Session is timing out, stopping it by sending a DELETE
LOG.log(Level.INFO, () -> String.format("Session id %s timed out, stopping...", id));
span.setStatus(Status.CANCELLED);
span.addEvent(String.format("Stopping the the timed session %s", id), attributeMap);
} else {
closeReason = SessionClosedReason.QUIT_COMMAND;
LOG.log(Level.INFO, () -> String.format("Session id %s is stopping on demand...", id));
span.addEvent(String.format("Stopping the session %s on demand", id), attributeMap);
}
Expand All @@ -354,8 +360,8 @@ private void stopTimedOutSession(SessionId id, SessionSlot slot, RemovalCause ca
String.format("Exception while trying to stop session %s", id), attributeMap);
}
}
// Attempt to stop the session
slot.stop();
// Attempt to stop the session with the appropriate reason
slot.stop(closeReason);
// Decrement pending sessions if Node is draining
if (this.isDraining()) {
int done = pendingSessions.decrementAndGet();
Expand Down
9 changes: 7 additions & 2 deletions java/src/org/openqa/selenium/grid/node/local/SessionSlot.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.openqa.selenium.events.EventBus;
import org.openqa.selenium.grid.data.CreateSessionRequest;
import org.openqa.selenium.grid.data.SessionClosedEvent;
import org.openqa.selenium.grid.data.SessionClosedReason;
import org.openqa.selenium.grid.node.ActiveSession;
import org.openqa.selenium.grid.node.SessionFactory;
import org.openqa.selenium.grid.node.relay.RelaySessionFactory;
Expand Down Expand Up @@ -104,6 +105,10 @@ public ActiveSession getSession() {
}

public void stop() {
stop(SessionClosedReason.QUIT_COMMAND);
}

public void stop(SessionClosedReason reason) {
if (isAvailable()) {
return;
}
Expand All @@ -117,8 +122,8 @@ public void stop() {
currentSession = null;
connectionCounter.set(0);
release();
bus.fire(new SessionClosedEvent(id));
LOG.info(String.format("Stopping session %s", id));
bus.fire(new SessionClosedEvent(id, reason));
LOG.info(String.format("Stopping session %s (reason: %s)", id, reason));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ public JdbcBackedSessionMap(Tracer tracer, Connection jdbcConnection, EventBus b
this.bus = Require.nonNull("Event bus", bus);

this.connection = jdbcConnection;
this.bus.addListener(SessionClosedEvent.listener(this::remove));
// Listen to SessionClosedEvent and extract the sessionId
this.bus.addListener(SessionClosedEvent.sessionListener(this::remove));

this.bus.addListener(
NodeRemovedEvent.listener(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
load("@rules_jvm_external//:defs.bzl", "artifact")
load("//java:defs.bzl", "java_library")

java_library(
Expand All @@ -16,5 +17,6 @@ java_library(
"//java/src/org/openqa/selenium/grid/server",
"//java/src/org/openqa/selenium/grid/sessionmap",
"//java/src/org/openqa/selenium/remote",
artifact("com.github.ben-manes.caffeine:caffeine"),
],
)
Loading