Skip to content

Commit c34aca8

Browse files
authored
Merge pull request #25693 from arjantijms/cherry_pick_80_listener_leak
Fix leak in the events listener storage
2 parents 1decd79 + 2ea846a commit c34aca8

File tree

2 files changed

+84
-38
lines changed

2 files changed

+84
-38
lines changed

nucleus/core/kernel/src/main/java/org/glassfish/kernel/event/EventsImpl.java

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,14 @@
1717

1818
package org.glassfish.kernel.event;
1919

20-
import java.lang.reflect.Method;
2120
import java.util.Arrays;
2221
import java.util.Map;
22+
import java.util.concurrent.ConcurrentHashMap;
2323
import java.util.concurrent.ConcurrentSkipListMap;
2424
import java.util.concurrent.ExecutorService;
2525
import java.util.concurrent.Executors;
2626
import java.util.concurrent.ThreadFactory;
2727
import java.util.concurrent.atomic.AtomicLong;
28-
import java.util.logging.Level;
2928
import java.util.logging.Logger;
3029

3130
import org.glassfish.api.event.EventListener;
@@ -37,6 +36,9 @@
3736
import org.glassfish.hk2.api.PostConstruct;
3837
import org.glassfish.kernel.KernelLoggerInfo;
3938

39+
import static java.util.logging.Level.SEVERE;
40+
import static java.util.logging.Level.WARNING;
41+
4042
/**
4143
* Simple implementation of the events dispatching facility.
4244
*
@@ -51,7 +53,8 @@ public class EventsImpl implements Events, PostConstruct {
5153
/**
5254
* Use skip list based map to preserve listeners in registration order.
5355
*/
54-
final Map<Listener, EventMatcher> listeners = new ConcurrentSkipListMap<>();
56+
final Map<Listener, EventMatcher> listenersBySequence = new ConcurrentSkipListMap<>();
57+
final Map<EventListener, Listener> listenersByIdentity = new ConcurrentHashMap<>();
5558

5659
private ExecutorService executor;
5760

@@ -70,22 +73,43 @@ public void postConstruct() {
7073
@Override
7174
public void register(EventListener listener) {
7275
try {
73-
Method eventMethod = listener.getClass().getMethod("event", Event.class);
74-
RestrictTo[] restrictTo = eventMethod.getParameters()[0].getAnnotationsByType(RestrictTo.class);
75-
EventTypes<?>[] eventTypes = Arrays.stream(restrictTo)
76-
.map(restrict -> EventTypes.create(restrict.value())).toArray(EventTypes[]::new);
77-
listeners.putIfAbsent(new Listener(listener, sequenceGenerator.getAndIncrement()), new EventMatcher(eventTypes));
76+
RestrictTo[] restrictTo =
77+
listener.getClass()
78+
.getMethod("event", Event.class)
79+
.getParameters()[0]
80+
.getAnnotationsByType(RestrictTo.class);
81+
82+
listenersByIdentity.computeIfAbsent(listener, e -> {
83+
84+
Listener listenerWrapperWithSeq = new Listener(listener, sequenceGenerator.getAndIncrement());
85+
86+
EventMatcher eventMatcher = new EventMatcher(
87+
Arrays.stream(restrictTo)
88+
.map(restrict -> EventTypes.create(restrict.value()))
89+
.toArray(EventTypes[]::new));
90+
91+
listenersBySequence.put(
92+
listenerWrapperWithSeq,
93+
eventMatcher);
94+
95+
return listenerWrapperWithSeq;
96+
});
97+
98+
7899
} catch (Throwable t) {
79-
// We need to catch Throwable, otherwise we can server not to
80-
// shutdown when the following happens:
100+
// We need to catch Throwable, otherwise we can not
101+
// shutdown the server when the following happens:
102+
//
81103
// Assume a bundle which has registered an event listener
82104
// has been uninstalled without unregistering the listener.
105+
//
83106
// listener.getClass() refers to a class of such an uninstalled
84-
// bundle. If framework has been refreshed, then the
107+
// bundle. If the framework has been refreshed, then the
85108
// classloader can't be used further to load any classes.
109+
//
86110
// As a result, an exception like NoClassDefFoundError is thrown
87111
// from getMethod.
88-
LOG.log(Level.SEVERE, KernelLoggerInfo.exceptionRegisterEventListener, t);
112+
LOG.log(SEVERE, KernelLoggerInfo.exceptionRegisterEventListener, t);
89113
}
90114
}
91115

@@ -96,28 +120,32 @@ public void send(final Event<?> event) {
96120

97121
@Override
98122
public void send(final Event<?> event, boolean asynchronously) {
99-
for (Map.Entry<Listener, EventMatcher> entry : listeners.entrySet()) {
100-
EventMatcher matcher = entry.getValue();
123+
for (var entry : listenersBySequence.entrySet()) {
124+
101125
// Check if the listener is interested with his event.
102-
if (matcher.matches(event)) {
126+
if (entry.getValue().matches(event)) {
127+
103128
Listener listener = entry.getKey();
129+
104130
if (asynchronously) {
131+
// Process Async
105132
executor.submit(() -> {
106133
try {
107134
listener.event(event);
108135
} catch (Throwable t) {
109-
LOG.log(Level.WARNING, KernelLoggerInfo.exceptionDispatchEvent, t);
136+
LOG.log(WARNING, KernelLoggerInfo.exceptionDispatchEvent, t);
110137
}
111138
});
112139
} else {
140+
// Process sync
113141
try {
114142
listener.event(event);
115143
} catch (DeploymentException e) {
116144
// When synchronous listener throws DeploymentException
117145
// we re-throw the exception to abort the deployment
118146
throw e;
119147
} catch (Throwable t) {
120-
LOG.log(Level.WARNING, KernelLoggerInfo.exceptionDispatchEvent, t);
148+
LOG.log(WARNING, KernelLoggerInfo.exceptionDispatchEvent, t);
121149
}
122150
}
123151
}
@@ -126,7 +154,12 @@ public void send(final Event<?> event, boolean asynchronously) {
126154

127155
@Override
128156
public boolean unregister(EventListener listener) {
129-
return listeners.remove(new Listener(listener)) != null;
157+
Listener listenerWrapperWithSeq = listenersByIdentity.remove(listener);
158+
if (listenerWrapperWithSeq == null) {
159+
return false;
160+
}
161+
162+
return listenersBySequence.remove(listenerWrapperWithSeq) != null;
130163
}
131164

132165
/**
@@ -158,23 +191,26 @@ EventListener unwrap() {
158191

159192
@Override
160193
public boolean equals(Object obj) {
161-
if (obj == this) {
162-
return true;
163-
}
164-
if (!(obj instanceof Listener)) {
165-
return false;
166-
}
167-
return eventListener.equals(((Listener) obj).eventListener);
194+
return
195+
obj instanceof Listener listener &&
196+
this.eventListener == listener.eventListener;
168197
}
169198

170199
@Override
171200
public int hashCode() {
172-
return eventListener.hashCode();
201+
return System.identityHashCode(eventListener);
173202
}
174203

175204
@Override
176205
public int compareTo(Listener listener) {
177-
return Long.compare(sequenceNumber, listener.sequenceNumber);
206+
// Treat the same underlying listener as the SAME KEY
207+
if (this.eventListener == listener.eventListener) {
208+
// Identity
209+
return 0;
210+
}
211+
212+
// otherwise keep registration order via sequence
213+
return Long.compare(this.sequenceNumber, listener.sequenceNumber);
178214
}
179215
}
180216

nucleus/core/kernel/src/main/java/org/glassfish/kernel/event/OSGiAwareEventsImpl.java

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024 Contributors to the Eclipse Foundation.
2+
* Copyright (c) 2024, 2025 Contributors to the Eclipse Foundation.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -23,7 +23,9 @@
2323
import org.osgi.framework.BundleContext;
2424
import org.osgi.framework.FrameworkEvent;
2525
import org.osgi.framework.FrameworkListener;
26-
import org.osgi.framework.FrameworkUtil;
26+
27+
import static org.osgi.framework.FrameworkEvent.PACKAGES_REFRESHED;
28+
import static org.osgi.framework.FrameworkUtil.getBundle;
2729

2830
/**
2931
* OSGi-aware implementation of the events dispatching facility.
@@ -48,25 +50,33 @@ public void removeFrameworkListener() {
4850

4951
@Override
5052
public void frameworkEvent(FrameworkEvent frameworkEvent) {
51-
if (frameworkEvent.getType() == FrameworkEvent.PACKAGES_REFRESHED) {
53+
if (frameworkEvent.getType() == PACKAGES_REFRESHED) {
54+
5255
// Get System Bundle context
5356
BundleContext bundleContext = frameworkEvent.getBundle().getBundleContext();
57+
5458
// Uninstalled bundles has been removed when framework has been refreshed.
5559
// Remove listeners registered by this uninstalled bundles.
56-
listeners.keySet().removeIf(listener -> {
60+
listenersBySequence.keySet().removeIf(listener -> {
61+
5762
// Get bundle for event listener
58-
Bundle bundle = FrameworkUtil.getBundle(listener.unwrap().getClass());
59-
if (bundle != null) {
60-
// Even if Bundle has been removed, it must preserve bundle_id.
61-
return bundleContext.getBundle(bundle.getBundleId()) == null;
63+
Bundle bundle = getBundle(listener.unwrap().getClass());
64+
if (bundle == null) {
65+
return true;
6266
}
63-
return true;
67+
68+
// Even if Bundle has been removed, it must preserve bundle_id.
69+
return bundleContext.getBundle(bundle.getBundleId()) == null;
6470
});
6571
}
6672
}
6773

6874
private BundleContext getBundleContext() {
69-
Bundle bundle = FrameworkUtil.getBundle(getClass());
70-
return bundle != null ? bundle.getBundleContext() : null;
75+
Bundle bundle = getBundle(getClass());
76+
if (bundle == null) {
77+
return null;
78+
}
79+
80+
return bundle.getBundleContext();
7181
}
7282
}

0 commit comments

Comments
 (0)