From 1825659f0a88e44f0999bc5cf401cba77e18ac67 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Tue, 4 May 2021 10:56:16 -0400 Subject: [PATCH] GP-917: Implementing and using PrivatelyQueuedListener for slow model callbacks. --- .../gui/objects/DebuggerObjectsProvider.java | 6 +- .../service/model/DefaultTraceRecorder.java | 6 +- .../service/model/TraceEventListener.java | 11 +- .../service/model/TraceObjectListener.java | 8 +- .../ghidra/util/datastruct/ListenerMap.java | 5 +- .../datastruct/PrivatelyQueuedListener.java | 101 ++++++++++++++++++ 6 files changed, 128 insertions(+), 9 deletions(-) create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/datastruct/PrivatelyQueuedListener.java diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java index 97837349ac..cbdf782552 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java @@ -68,6 +68,7 @@ import ghidra.program.model.listing.Program; import ghidra.trace.model.Trace; import ghidra.trace.model.thread.TraceThread; import ghidra.util.*; +import ghidra.util.datastruct.PrivatelyQueuedListener; import ghidra.util.table.GhidraTable; import resources.ResourceManager; @@ -1523,6 +1524,9 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter class MyObjectListener extends AnnotatedDebuggerAttributeListener { protected final DebuggerCallbackReorderer reorderer = new DebuggerCallbackReorderer(this); + protected final PrivatelyQueuedListener queue = + new PrivatelyQueuedListener<>(DebuggerModelListener.class, "ObjectsProvider-EventQueue", + reorderer); public MyObjectListener() { super(MethodHandles.lookup()); @@ -1768,7 +1772,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter } public DebuggerModelListener getListener() { - return listener.reorderer; + return listener.queue.in; } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java index 5ecebdb46f..91e384e30c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java @@ -16,7 +16,9 @@ package ghidra.app.plugin.core.debug.service.model; import java.util.*; -import java.util.concurrent.CompletableFuture; +import java.util.concurrent.*; + +import org.apache.commons.lang3.concurrent.BasicThreadFactory; import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.service.model.interfaces.*; @@ -71,6 +73,8 @@ public class DefaultTraceRecorder implements TraceRecorder { //protected final PermanentTransactionExecutor seqTx; protected final PermanentTransactionExecutor parTx; + protected final Executor privateQueue = Executors.newSingleThreadExecutor( + new BasicThreadFactory.Builder().namingPattern("DTR-EventQueue-%d").build()); protected final AsyncLazyValue lazyInit = new AsyncLazyValue<>(this::doInit); private boolean valid = true; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceEventListener.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceEventListener.java index 5016df74f9..a589345113 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceEventListener.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceEventListener.java @@ -21,8 +21,7 @@ import java.util.*; import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedStackRecorder; import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedThreadRecorder; -import ghidra.dbg.AnnotatedDebuggerAttributeListener; -import ghidra.dbg.DebuggerObjectModel; +import ghidra.dbg.*; import ghidra.dbg.error.DebuggerMemoryAccessException; import ghidra.dbg.target.*; import ghidra.dbg.target.TargetEventScope.TargetEventType; @@ -37,6 +36,7 @@ import ghidra.trace.model.memory.TraceMemoryState; import ghidra.trace.model.modules.TraceModule; import ghidra.util.Msg; import ghidra.util.TimedMsg; +import ghidra.util.datastruct.PrivatelyQueuedListener; import ghidra.util.exception.DuplicateNameException; public class TraceEventListener extends AnnotatedDebuggerAttributeListener { @@ -48,10 +48,15 @@ public class TraceEventListener extends AnnotatedDebuggerAttributeListener { private boolean valid = true; protected final DebuggerCallbackReorderer reorderer = new DebuggerCallbackReorderer(this); + protected final PrivatelyQueuedListener queue; public TraceEventListener(TraceObjectManager collection) { super(MethodHandles.lookup()); this.recorder = collection.getRecorder(); + + this.queue = new PrivatelyQueuedListener<>(DebuggerModelListener.class, + recorder.privateQueue, reorderer); + this.target = recorder.getTarget(); this.trace = recorder.getTrace(); this.memoryManager = trace.getMemoryManager(); @@ -59,7 +64,7 @@ public class TraceEventListener extends AnnotatedDebuggerAttributeListener { public void init() { DebuggerObjectModel model = target.getModel(); - model.addModelListener(reorderer, true); + model.addModelListener(queue.in, true); } private boolean successor(TargetObject ref) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceObjectListener.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceObjectListener.java index 5b73d45b01..84845cdfe9 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceObjectListener.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceObjectListener.java @@ -25,6 +25,7 @@ import ghidra.dbg.target.*; import ghidra.dbg.util.DebuggerCallbackReorderer; import ghidra.dbg.util.PathUtils.PathComparator; import ghidra.util.Msg; +import ghidra.util.datastruct.PrivatelyQueuedListener; public class TraceObjectListener implements DebuggerModelListener { @@ -35,10 +36,15 @@ public class TraceObjectListener implements DebuggerModelListener { protected final NavigableMap, TargetObject> initialized = new TreeMap<>(PathComparator.KEYED); protected final DebuggerCallbackReorderer reorderer = new DebuggerCallbackReorderer(this); + protected final PrivatelyQueuedListener queue; public TraceObjectListener(TraceObjectManager manager) { this.objectManager = manager; this.target = objectManager.getTarget(); + + DefaultTraceRecorder recorder = objectManager.getRecorder(); + this.queue = new PrivatelyQueuedListener<>(DebuggerModelListener.class, + recorder.privateQueue, reorderer); } public void init() { @@ -47,7 +53,7 @@ public class TraceObjectListener implements DebuggerModelListener { processInit(added); } DebuggerObjectModel model = target.getModel(); - model.addModelListener(reorderer, true); + model.addModelListener(queue.in, true); }); } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/datastruct/ListenerMap.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/datastruct/ListenerMap.java index d1c1cc600c..a7b4aea432 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/datastruct/ListenerMap.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/datastruct/ListenerMap.java @@ -206,12 +206,11 @@ public class ListenerMap { * * @param iface the interface to multiplex */ - @SuppressWarnings("unchecked") public ListenerMap(Class

iface, Executor executor) { this.iface = Objects.requireNonNull(iface); this.executor = executor; - this.fire = (P) Proxy.newProxyInstance(this.getClass().getClassLoader(), - new Class[] { iface }, new ListenerHandler<>(iface)); + this.fire = iface.cast(Proxy.newProxyInstance(this.getClass().getClassLoader(), + new Class[] { iface }, new ListenerHandler<>(iface))); } @Override diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/datastruct/PrivatelyQueuedListener.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/datastruct/PrivatelyQueuedListener.java new file mode 100644 index 0000000000..898af53ddb --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/datastruct/PrivatelyQueuedListener.java @@ -0,0 +1,101 @@ +/* ### + * IP: GHIDRA + * + * Licensed 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 ghidra.util.datastruct; + +import java.lang.reflect.*; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +import org.apache.commons.lang3.concurrent.BasicThreadFactory; + +/** + * A listener which queues invocations onto a separate executor + * + * @param

the type of listener + */ +public class PrivatelyQueuedListener

{ + + protected class ListenerHandler implements InvocationHandler { + protected final Class

iface; + + public ListenerHandler(Class

iface) { + this.iface = iface; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + executor.execute(() -> { + try { + method.invoke(out, args); + } + catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + ListenerMap.reportError(out, cause); + } + catch (Throwable e) { + ListenerMap.reportError(out, e); + } + }); + return null; // Assumes void return type + } + } + + /** + * The "input" listener, which should be added as a listener on other things + */ + public final P in; + + protected final Executor executor; + protected final P out; + + /** + * Create a new privately-queued listener which will invoke the given "output" listener + * + *

+ * Invoking the listener methods of {@link #in} will cause that invocation to be queued and + * eventually delivered to the given output listener. Note, as a result, it is assumed all + * listener methods return type {@code void}, since returning a value would require waiting on + * the invocation to complete, which defeats the purpose of the private queue. The invocations + * on {@link #in} will always return {@code null}, which will cause an exception if the return + * type is a different primitive. + * + * @param iface the interface of the listener + * @param executor the executor representing the processing queue + * @param out the listener to receive the queued invocations + */ + public PrivatelyQueuedListener(Class

iface, Executor executor, P out) { + this.in = iface.cast(Proxy.newProxyInstance(this.getClass().getClassLoader(), + new Class[] { iface }, new ListenerHandler(iface))); + this.executor = executor; + this.out = out; + } + + /** + * Create a new single-threaded privately-queued listener + * + * @see {@link #PrivatelyQueuedListener(Class, Executor, Object)} + * @param iface the interface of the listener + * @param threadNamePattern a pattern for naming the single thread + * @param out the listener to receive the queued invocations + */ + public PrivatelyQueuedListener(Class

iface, String threadNamePattern, P out) { + this(iface, + Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder() + .namingPattern(threadNamePattern) + .build()), + out); + } +}