Skip to content

Commit 2c5b318

Browse files
committed
Adds mbean server to track trigger states
1 parent 2511d26 commit 2c5b318

File tree

4 files changed

+125
-13
lines changed

4 files changed

+125
-13
lines changed

exist-core/src/main/java/org/exist/collections/triggers/TriggerStatePerThread.java

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@
2626
import org.exist.xmldb.XmldbURI;
2727

2828
import javax.annotation.Nullable;
29+
import java.lang.ref.WeakReference;
2930
import java.util.ArrayDeque;
31+
import java.util.Collections;
3032
import java.util.Deque;
3133
import java.util.Iterator;
34+
import java.util.Map;
3235
import java.util.Objects;
33-
import java.util.concurrent.ConcurrentHashMap;
34-
import java.util.concurrent.ConcurrentMap;
36+
import java.util.WeakHashMap;
3537
import java.util.function.BiConsumer;
3638
import java.util.function.Consumer;
3739

@@ -42,11 +44,10 @@
4244
* @author <a href="mailto:[email protected]">Adam Retter</a>
4345
*/
4446
public class TriggerStatePerThread {
45-
46-
private static final ConcurrentMap<Txn, Deque<TriggerState>> TRIGGER_STATES = new ConcurrentHashMap<>();
47+
private static final Map<Thread, TriggerStates> TRIGGER_STATES = Collections.synchronizedMap(new WeakHashMap<>());
4748

4849
public static void setAndTest(final Txn txn, final Trigger trigger, final TriggerPhase triggerPhase, final TriggerEvent triggerEvent, final XmldbURI src, final @Nullable XmldbURI dst) throws CyclicTriggerException {
49-
final Deque<TriggerState> states = getStates(txn);
50+
final TriggerStates states = getStates(txn);
5051

5152
if (states.isEmpty()) {
5253
if (triggerPhase != TriggerPhase.BEFORE) {
@@ -123,7 +124,7 @@ public static void clearIfFinished(final Txn txn, final TriggerPhase phase) {
123124
if (phase == TriggerPhase.AFTER) {
124125

125126
int depth = 0;
126-
final Deque<TriggerState> states = getStates(txn);
127+
final TriggerStates states = getStates(txn);
127128
for (final Iterator<TriggerState> it = states.descendingIterator(); it.hasNext(); ) {
128129
final TriggerState state = it.next();
129130
switch (state.triggerPhase) {
@@ -144,25 +145,37 @@ public static void clearIfFinished(final Txn txn, final TriggerPhase phase) {
144145
}
145146
}
146147

148+
public static int keys() {
149+
return TRIGGER_STATES.size();
150+
}
151+
152+
public static void clearAll() {
153+
TRIGGER_STATES.clear();
154+
}
155+
147156
public static void clear(final Txn txn) {
148-
TRIGGER_STATES.remove(txn);
157+
TRIGGER_STATES.remove(Thread.currentThread());
149158
}
150159

151160
public static boolean isEmpty(final Txn txn) {
152161
return getStates(txn).isEmpty();
153162
}
154163

155-
public static void forEach(BiConsumer<Txn, Deque<TriggerState>> action) {
164+
public static void dumpTriggerStates() {
165+
TRIGGER_STATES.forEach((k, s) -> System.err.format("key: %s, size: %s", k, s.size()).println());
166+
}
167+
168+
public static void forEach(BiConsumer<Thread, TriggerStates> action) {
156169
TRIGGER_STATES.forEach(action);
157170
}
158171

159-
private static Deque<TriggerState> getStates(final Txn txn) {
160-
return TRIGGER_STATES.computeIfAbsent(txn, TriggerStatePerThread::initStates);
172+
private static TriggerStates getStates(final Txn txn) {
173+
return TRIGGER_STATES.computeIfAbsent(Thread.currentThread(), key -> new TriggerStates());
161174
}
162175

163-
private static Deque<TriggerState> initStates(final Txn txn) {
176+
private static TriggerStates initStates(final Txn txn) {
164177
txn.registerListener(new TransactionCleanUp(txn, TriggerStatePerThread::clear));
165-
return new ArrayDeque<>();
178+
return new TriggerStates();
166179
}
167180

168181
public record TransactionCleanUp(Txn txn, Consumer<Txn> consumer) implements TxnListener {
@@ -177,6 +190,36 @@ public void abort() {
177190
}
178191
}
179192

193+
public static final class TriggerStates extends WeakReference<Deque<TriggerState>> {
194+
public TriggerStates() {
195+
super(new ArrayDeque<>());
196+
}
197+
198+
public Iterator<TriggerState> descendingIterator() {
199+
return get().descendingIterator();
200+
}
201+
202+
public boolean isEmpty() {
203+
return get().isEmpty();
204+
}
205+
206+
public int size() {
207+
return get().size();
208+
}
209+
210+
public Iterator<TriggerState> iterator() {
211+
return get().iterator();
212+
}
213+
214+
public TriggerState peekFirst() {
215+
return get().peekFirst();
216+
}
217+
218+
public void addFirst(TriggerState newState) {
219+
get().addFirst(newState);
220+
}
221+
}
222+
180223
public record TriggerState(Trigger trigger, TriggerPhase triggerPhase, TriggerEvent triggerEvent, XmldbURI src,
181224
@Nullable XmldbURI dst, boolean possiblyCyclic) {
182225

exist-core/src/main/java/org/exist/collections/triggers/XQueryTrigger.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,11 @@ public class XQueryTrigger extends SAXTrigger implements DocumentTrigger, Collec
119119
private String bindingPrefix = null;
120120
private XQuery service;
121121

122-
public final static String PREPARE_EXCEPTION_MESSAGE = "Error during trigger prepare";
122+
public static final String PREPARE_EXCEPTION_MESSAGE = "Error during trigger prepare";
123+
124+
public XQueryTrigger() {
125+
XQueryTriggerMBeanImpl.init();
126+
}
123127

124128
@Override
125129
public void configure(final DBBroker broker, final Txn transaction, final Collection parent, final Map<String, List<?>> parameters) throws TriggerException {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.exist.collections.triggers;
2+
3+
public interface XQueryTriggerMBean {
4+
int getKeys();
5+
6+
void clear();
7+
8+
String dumpTriggerStates();
9+
10+
String listKeys();
11+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package org.exist.collections.triggers;
2+
3+
import javax.management.InstanceAlreadyExistsException;
4+
import javax.management.MBeanRegistrationException;
5+
import javax.management.MBeanServer;
6+
import javax.management.MalformedObjectNameException;
7+
import javax.management.NotCompliantMBeanException;
8+
import javax.management.ObjectName;
9+
import javax.management.StandardMBean;
10+
import java.lang.management.ManagementFactory;
11+
import java.util.StringJoiner;
12+
13+
final class XQueryTriggerMBeanImpl extends StandardMBean implements XQueryTriggerMBean {
14+
15+
private XQueryTriggerMBeanImpl() throws NotCompliantMBeanException {
16+
super(XQueryTriggerMBean.class);
17+
}
18+
19+
static void init() {
20+
try {
21+
final ObjectName name = ObjectName.getInstance("org.exist.management.exist", "type", "TriggerStates");
22+
final MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer();
23+
if (!platformMBeanServer.isRegistered(name)) {
24+
platformMBeanServer.registerMBean(new XQueryTriggerMBeanImpl(), name);
25+
}
26+
} catch (final MalformedObjectNameException | InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException ex) {
27+
ex.printStackTrace();
28+
}
29+
}
30+
31+
@Override
32+
public int getKeys() {
33+
return TriggerStatePerThread.keys();
34+
}
35+
36+
@Override
37+
public void clear() {
38+
TriggerStatePerThread.clearAll();
39+
}
40+
41+
@Override
42+
public String dumpTriggerStates() {
43+
StringJoiner joiner = new StringJoiner("\n");
44+
TriggerStatePerThread.forEach((k, v) -> joiner.add("%s: %s".formatted(k, v.size())));
45+
return joiner.toString();
46+
}
47+
48+
@Override
49+
public String listKeys() {
50+
StringJoiner joiner = new StringJoiner("\n");
51+
TriggerStatePerThread.forEach((k, v) -> joiner.add("%s".formatted(k)));
52+
return joiner.toString();
53+
}
54+
}

0 commit comments

Comments
 (0)