Skip to content

Commit b8386a1

Browse files
kevink-sqkohlschuetter
authored andcommitted
Fix concurrency issue from modifying the selectedKeysSet
This bug may have been a regression introduced in optimizing the key removal: a7c3106 To reproduce refer to poc: https://github.com/kevink-sq/jetty-concurrency-issue-poc To verify fix, refer to poc branch (same as these changes): https://github.com/kevink-sq/jetty-concurrency-issue-poc/tree/kevink/attempted-junixsocket-fix-2 Jetty's [ManagedSelector](https://github.com/jetty/jetty.project/blob/7a7d69a69f4f51772e20813332291189a24e91b1/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java#L655) iterates through the selectedKeysSet and when separate processes cancel the AFSelectorKey, the iterator throws a `ConcurrentModificationException`. Fix is to introduce `cancelledKeysSet` to defer removal of cancelled keys until the select process. Use of the cancelled sets are referenced from java.nio.channels.spi's `AbstractSelector` and `AbstractSelectionKey`. Ideally we'd have `AFSelectionKey` extend from `AbstractSelectionKey` to leverage the base class's internal cancelled set but`AbstractSelectionKey` defines its own `isValid` and `cancel` and cannot override the base's final methods. Also added an atomic check to `AFSelectionKey#cancel` to prevent "Too many open files" socket exception.
1 parent 43cbb88 commit b8386a1

2 files changed

Lines changed: 23 additions & 2 deletions

File tree

junixsocket-common/src/main/java/org/newsclub/net/unix/AFSelectionKey.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,10 @@ boolean isSelected() {
8080

8181
@Override
8282
public void cancel() {
83-
sel.remove(this);
84-
cancelNoRemove();
83+
if (!cancelled.compareAndSet(false, true) || !chann.isOpen()) {
84+
return;
85+
}
86+
sel.prepareRemove(this);
8587
}
8688

8789
void cancelNoRemove() {

junixsocket-common/src/main/java/org/newsclub/net/unix/AFSelector.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ final class AFSelector extends AbstractSelector {
4949

5050
private final Set<SelectionKey> selectedKeysSet = new HashSet<>();
5151
private final Set<SelectionKey> selectedKeysPublic = new UngrowableSet<>(selectedKeysSet);
52+
private final Set<SelectionKey> cancelledKeysSet = new HashSet<>();
5253

5354
private PollFd pollFd = null;
5455

@@ -112,6 +113,7 @@ private int select0(int timeout) throws IOException {
112113
throw new ClosedSelectorException();
113114
}
114115
pfd = pollFd = initPollFd(pollFd);
116+
performRemove();
115117
selectedKeysSet.clear();
116118
}
117119
int num;
@@ -299,6 +301,23 @@ synchronized void remove(AFSelectionKey key) {
299301
pollFd = null;
300302
}
301303

304+
void prepareRemove(AFSelectionKey key) {
305+
synchronized (cancelledKeysSet) {
306+
cancelledKeysSet.add(key);
307+
}
308+
}
309+
310+
void performRemove() {
311+
synchronized (cancelledKeysSet) {
312+
for (SelectionKey key : cancelledKeysSet) {
313+
selectedKeysSet.remove(key);
314+
deregister((AFSelectionKey) key);
315+
pollFd = null;
316+
}
317+
cancelledKeysSet.clear();
318+
}
319+
}
320+
302321
private void deregister(AFSelectionKey key) {
303322
// super.deregister unnecessarily casts SelectionKey to AbstractSelectionKey, and
304323
// ((AbstractSelectableChannel)key.channel()).removeKey(key); is not visible.

0 commit comments

Comments
 (0)