Skip to content

Commit 7a36ee0

Browse files
committed
Add suggestion to improve code
Signed-off-by: Viet Nguyen Duc <[email protected]>
1 parent 0a2f27c commit 7a36ee0

File tree

1 file changed

+42
-124
lines changed

1 file changed

+42
-124
lines changed

java/src/org/openqa/selenium/grid/sessionmap/local/LocalSessionMap.java

Lines changed: 42 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,14 @@
2121
import static org.openqa.selenium.remote.RemoteTags.SESSION_ID_EVENT;
2222

2323
import java.net.URI;
24-
import java.util.ArrayList;
2524
import java.util.Collection;
25+
import java.util.Collections;
2626
import java.util.HashMap;
2727
import java.util.HashSet;
2828
import java.util.Map;
2929
import java.util.Set;
3030
import java.util.concurrent.ConcurrentHashMap;
3131
import java.util.concurrent.ConcurrentMap;
32-
import java.util.concurrent.locks.ReadWriteLock;
33-
import java.util.concurrent.locks.ReentrantReadWriteLock;
3432
import java.util.logging.Logger;
3533
import org.openqa.selenium.NoSuchSessionException;
3634
import org.openqa.selenium.events.Event;
@@ -92,17 +90,21 @@ public boolean isReady() {
9290
@Override
9391
public boolean add(Session session) {
9492
Require.nonNull("Session", session);
95-
SessionId id = session.getId();
9693

97-
// IndexedSessionMap now handles internal synchronization
94+
SessionId id = session.getId();
9895
knownSessions.put(id, session);
9996

10097
try (Span span = tracer.getCurrentContext().createSpan("local_sessionmap.add")) {
10198
AttributeMap attributeMap = tracer.createAttributeMap();
10299
attributeMap.put(AttributeKey.LOGGER_CLASS.getKey(), getClass().getName());
103100
SESSION_ID.accept(span, id);
104101
SESSION_ID_EVENT.accept(attributeMap, id);
105-
span.addEvent("Added session into local Session Map", attributeMap);
102+
103+
String sessionAddedMessage =
104+
String.format(
105+
"Added session to local Session Map, Id: %s, Node: %s", id, session.getUri());
106+
span.addEvent(sessionAddedMessage, attributeMap);
107+
LOG.info(sessionAddedMessage);
106108
}
107109

108110
return true;
@@ -112,7 +114,6 @@ public boolean add(Session session) {
112114
public Session get(SessionId id) {
113115
Require.nonNull("Session ID", id);
114116

115-
// IndexedSessionMap now handles internal synchronization
116117
Session session = knownSessions.get(id);
117118
if (session == null) {
118119
throw new NoSuchSessionException("Unable to find session with ID: " + id);
@@ -124,14 +125,14 @@ public Session get(SessionId id) {
124125
public void remove(SessionId id) {
125126
Require.nonNull("Session ID", id);
126127

127-
// IndexedSessionMap now handles internal synchronization
128128
Session removedSession = knownSessions.remove(id);
129129

130130
try (Span span = tracer.getCurrentContext().createSpan("local_sessionmap.remove")) {
131131
AttributeMap attributeMap = tracer.createAttributeMap();
132132
attributeMap.put(AttributeKey.LOGGER_CLASS.getKey(), getClass().getName());
133133
SESSION_ID.accept(span, id);
134134
SESSION_ID_EVENT.accept(attributeMap, id);
135+
135136
String sessionDeletedMessage =
136137
String.format(
137138
"Deleted session from local Session Map, Id: %s, Node: %s",
@@ -142,57 +143,42 @@ public void remove(SessionId id) {
142143
}
143144
}
144145

145-
/** Batch remove sessions by URI with proper locking to prevent race conditions */
146146
private void batchRemoveByUri(URI externalUri, Class<? extends Event> eventClass) {
147-
// IndexedSessionMap now handles internal synchronization
148147
Set<SessionId> sessionsToRemove = knownSessions.getSessionsByUri(externalUri);
149-
if (!sessionsToRemove.isEmpty()) {
150-
knownSessions.batchRemove(sessionsToRemove);
148+
149+
if (sessionsToRemove.isEmpty()) {
150+
return; // Early return for empty operations - no tracing overhead
151151
}
152152

153+
knownSessions.batchRemove(sessionsToRemove);
154+
153155
try (Span span = tracer.getCurrentContext().createSpan("local_sessionmap.batch_remove")) {
154156
AttributeMap attributeMap = tracer.createAttributeMap();
155157
attributeMap.put(AttributeKey.LOGGER_CLASS.getKey(), getClass().getName());
156158
attributeMap.put("event.class", eventClass.getName());
157159
attributeMap.put("node.uri", externalUri.toString());
158160
attributeMap.put("sessions.count", sessionsToRemove.size());
159161

160-
LOG.info(
162+
String batchRemoveMessage =
161163
String.format(
162-
"Event %s triggered batch remove from local Session Map for Node %s",
163-
eventClass.getName(), externalUri));
164-
String eventMessage = "";
165-
if (!sessionsToRemove.isEmpty()) {
166-
eventMessage =
167-
String.format(
168-
"Batch removed %d sessions belonging to Node %s including: %s",
169-
sessionsToRemove.size(), externalUri, sessionsToRemove);
170-
} else {
171-
eventMessage =
172-
String.format(
173-
"No sessions found to remove from local Session Map for Node %s", externalUri);
174-
}
175-
span.addEvent(eventMessage, attributeMap);
176-
LOG.info(eventMessage);
164+
"Batch removed %d sessions from local Session Map for Node %s (triggered by %s)",
165+
sessionsToRemove.size(), externalUri, eventClass.getSimpleName());
166+
span.addEvent(batchRemoveMessage, attributeMap);
167+
LOG.info(batchRemoveMessage);
177168
}
178169
}
179170

180-
/** Custom ConcurrentMap implementation that automatically maintains a URI-to-SessionId index */
181171
private static class IndexedSessionMap {
182172
private final ConcurrentMap<SessionId, Session> sessions = new ConcurrentHashMap<>();
183173
private final ConcurrentMap<URI, Set<SessionId>> sessionsByUri = new ConcurrentHashMap<>();
184-
// Internal lock to ensure atomicity of multi-step operations across both maps
185-
private final ReadWriteLock internalLock = new ReentrantReadWriteLock();
174+
private final Object coordinationLock = new Object();
186175

187176
public Session get(SessionId id) {
188-
// Read operations are atomic on ConcurrentHashMap - no lock needed
189177
return sessions.get(id);
190178
}
191179

192180
public Session put(SessionId id, Session session) {
193-
// Write lock needed: multiple operations across both maps must be atomic
194-
internalLock.writeLock().lock();
195-
try {
181+
synchronized (coordinationLock) {
196182
Session previous = sessions.put(id, session);
197183

198184
if (previous != null && previous.getUri() != null) {
@@ -205,133 +191,69 @@ public Session put(SessionId id, Session session) {
205191
}
206192

207193
return previous;
208-
} finally {
209-
internalLock.writeLock().unlock();
210194
}
211195
}
212196

213197
public Session remove(SessionId id) {
214-
// Write lock needed: multiple operations across both maps must be atomic
215-
internalLock.writeLock().lock();
216-
try {
198+
synchronized (coordinationLock) {
217199
Session removed = sessions.remove(id);
218200

219201
if (removed != null && removed.getUri() != null) {
220202
cleanupUriIndex(removed.getUri(), id);
221203
}
222204

223205
return removed;
224-
} finally {
225-
internalLock.writeLock().unlock();
226206
}
227207
}
228208

229209
public void batchRemove(Set<SessionId> sessionIds) {
230-
// Write lock needed: multiple operations across both maps must be atomic
231-
internalLock.writeLock().lock();
232-
try {
210+
synchronized (coordinationLock) {
233211
Map<URI, Set<SessionId>> uriToSessionIds = new HashMap<>();
234212

213+
// Single loop: remove sessions and collect URI mappings in one pass
235214
for (SessionId id : sessionIds) {
236-
Session session = sessions.get(id);
215+
Session session = sessions.remove(id);
237216
if (session != null && session.getUri() != null) {
238217
uriToSessionIds.computeIfAbsent(session.getUri(), k -> new HashSet<>()).add(id);
239218
}
240219
}
241220

242-
for (SessionId id : sessionIds) {
243-
sessions.remove(id);
244-
}
245-
246-
// Robust cleanup for each URI
221+
// Clean up URI index for all affected URIs
247222
for (Map.Entry<URI, Set<SessionId>> entry : uriToSessionIds.entrySet()) {
248-
URI uri = entry.getKey();
249-
Set<SessionId> idsToRemove = entry.getValue();
250-
cleanupUriIndex(uri, idsToRemove);
223+
cleanupUriIndex(entry.getKey(), entry.getValue());
251224
}
252-
} finally {
253-
internalLock.writeLock().unlock();
254225
}
255226
}
256227

257-
/**
258-
* Robust cleanup of URI index to prevent memory leaks from empty sets. Handles single session
259-
* removal with explicit empty set cleanup.
260-
*/
261228
private void cleanupUriIndex(URI uri, SessionId sessionId) {
262-
Set<SessionId> sessionIds = sessionsByUri.get(uri);
263-
if (sessionIds != null) {
264-
sessionIds.remove(sessionId);
265-
// Explicit check and removal to prevent memory leaks
266-
if (sessionIds.isEmpty()) {
267-
sessionsByUri.remove(uri, sessionIds);
268-
}
269-
}
229+
sessionsByUri.computeIfPresent(
230+
uri,
231+
(key, sessionIds) -> {
232+
sessionIds.remove(sessionId);
233+
return sessionIds.isEmpty() ? null : sessionIds;
234+
});
270235
}
271236

272-
/**
273-
* Robust cleanup of URI index to prevent memory leaks from empty sets. Handles batch session
274-
* removal with explicit empty set cleanup.
275-
*/
276237
private void cleanupUriIndex(URI uri, Set<SessionId> sessionIdsToRemove) {
277-
Set<SessionId> sessionIds = sessionsByUri.get(uri);
278-
if (sessionIds != null) {
279-
sessionIds.removeAll(sessionIdsToRemove);
280-
// Explicit check and removal to prevent memory leaks
281-
if (sessionIds.isEmpty()) {
282-
sessionsByUri.remove(uri, sessionIds);
283-
}
284-
}
285-
}
286-
287-
/**
288-
* Periodic cleanup to remove any empty sets that may have been missed. Should be called
289-
* periodically to prevent memory leaks.
290-
*/
291-
public void performMaintenanceCleanup() {
292-
internalLock.writeLock().lock();
293-
try {
294-
// Find and remove empty URI sets
295-
Set<URI> emptyUris = new HashSet<>();
296-
for (Map.Entry<URI, Set<SessionId>> entry : sessionsByUri.entrySet()) {
297-
if (entry.getValue().isEmpty()) {
298-
emptyUris.add(entry.getKey());
299-
}
300-
}
301-
302-
for (URI emptyUri : emptyUris) {
303-
sessionsByUri.remove(emptyUri);
304-
}
305-
} finally {
306-
internalLock.writeLock().unlock();
307-
}
238+
sessionsByUri.computeIfPresent(
239+
uri,
240+
(key, sessionIds) -> {
241+
sessionIds.removeAll(sessionIdsToRemove);
242+
return sessionIds.isEmpty() ? null : sessionIds;
243+
});
308244
}
309245

310246
public Set<SessionId> getSessionsByUri(URI uri) {
311-
// Read operations are atomic on ConcurrentHashMap - no lock needed
312247
Set<SessionId> result = sessionsByUri.get(uri);
313-
// Return empty set instead of null, and ensure we don't return empty sets
314248
return (result != null && !result.isEmpty()) ? result : Set.of();
315249
}
316250

317251
public Set<Map.Entry<SessionId, Session>> entrySet() {
318-
// Read lock to ensure consistent view during iteration
319-
internalLock.readLock().lock();
320-
try {
321-
return new HashSet<>(sessions.entrySet());
322-
} finally {
323-
internalLock.readLock().unlock();
324-
}
252+
return Collections.unmodifiableSet(sessions.entrySet());
325253
}
326254

327255
public Collection<Session> values() {
328-
// Read lock to ensure consistent view during iteration
329-
internalLock.readLock().lock();
330-
try {
331-
return new ArrayList<>(sessions.values());
332-
} finally {
333-
internalLock.readLock().unlock();
334-
}
256+
return Collections.unmodifiableCollection(sessions.values());
335257
}
336258

337259
public int size() {
@@ -343,13 +265,9 @@ public boolean isEmpty() {
343265
}
344266

345267
public void clear() {
346-
// Write lock needed: multiple operations across both maps must be atomic
347-
internalLock.writeLock().lock();
348-
try {
268+
synchronized (coordinationLock) {
349269
sessions.clear();
350270
sessionsByUri.clear();
351-
} finally {
352-
internalLock.writeLock().unlock();
353271
}
354272
}
355273
}

0 commit comments

Comments
 (0)