Merge remote-tracking branch 'origin/patch'

This commit is contained in:
Ryan Kurtz 2025-07-28 12:44:07 +00:00
commit 598efa66d9
9 changed files with 151 additions and 19 deletions

View file

@ -700,4 +700,15 @@ public interface Target {
* @return true if busy * @return true if busy
*/ */
boolean isBusy(); boolean isBusy();
/**
* Forcibly commit all of the back-ends transactions on this target's trace.
*
* <p>
* This is generally not a recommended course of action, except that sometimes the back-end
* crashes and fails to close a transaction. It should only be invoked by a relatively hidden
* menu option, and mediated by a warning of some sort. Closing a transaction prematurely, when
* the back-end actually <em>does</em> still need it may cause a host of other problems.
*/
void forciblyCloseTransactions();
} }

View file

@ -171,8 +171,19 @@ public interface TraceRmiConnection extends AutoCloseable {
/** /**
* Check if the given target has a transaction open * Check if the given target has a transaction open
* *
* @param target * @param target the target
* @return true if busy * @return true if busy
*/ */
boolean isBusy(Target target); boolean isBusy(Target target);
/**
* Forcibly commit all transactions this connection has on the given trace
*
* <p>
* This may cause undefined behavior in the back-end, especially if it still needs the
* transaction.
*
* @param target the the target
*/
void forciblyCloseTransactions(Target target);
} }

View file

@ -105,5 +105,15 @@
<P>Stop the server. This closes the persistent server. This does not affect pending acceptors <P>Stop the server. This closes the persistent server. This does not affect pending acceptors
or established connections.</P> or established connections.</P>
<H3><A name="forcibly_close_txes"></A>Forcibly Close Transactions</H3>
<P>Forcibly close all the back-end's transactions on the target trace. This is generally not a
recommended course of action, except that sometimes the back-end crashes and fails to close a
transaction. Un-closed transactions from the back-end can leave most, if not all, of the UI in
a stale state, since event processing on the trace is disabled. If there is good reason to
believe the back-end has forgotten to close a transaction, this action will forcibly close all
of them and re-enable event processing. If, however, the back-end was in fact still doing work
with that transaction, it may crash and/or corrupt the connection.</P>
</BODY> </BODY>
</HTML> </HTML>

View file

@ -161,6 +161,24 @@ public class TraceRmiConnectionManagerProvider extends ComponentProviderAdapter
} }
} }
interface ForceCloseTransactionsActions {
String NAME = "Forcibly Close Transactions";
String DESCRIPTION = "Forcibly commit all remote transactions on the trace";
String GROUP = GROUP_MAINTENANCE;
String HELP_ANCHOR = "forcibly_close_txes";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.menuPath(NAME)
.popupMenuPath(NAME)
.menuGroup(GROUP)
.popupMenuGroup(GROUP)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
class InjectableGTree extends GTree { class InjectableGTree extends GTree {
public InjectableGTree(GTreeNode root) { public InjectableGTree(GTreeNode root) {
super(root); super(root);
@ -200,6 +218,7 @@ public class TraceRmiConnectionManagerProvider extends ComponentProviderAdapter
DockingAction actionConnectOutbound; DockingAction actionConnectOutbound;
DockingAction actionCloseConnection; DockingAction actionCloseConnection;
DockingAction actionCloseAll; DockingAction actionCloseAll;
DockingAction actionForceCloseTransactions;
TraceRmiManagerActionContext myActionContext; TraceRmiManagerActionContext myActionContext;
@ -309,6 +328,12 @@ public class TraceRmiConnectionManagerProvider extends ComponentProviderAdapter
.enabledWhen(this::isActionCloseAllEnabled) .enabledWhen(this::isActionCloseAllEnabled)
.onAction(this::doActionCloseAllActivated) .onAction(this::doActionCloseAllActivated)
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
actionForceCloseTransactions = ForceCloseTransactionsActions.builder(plugin)
.withContext(TraceRmiManagerActionContext.class)
.enabledWhen(this::isActionForceCloseTransactionsEnabled)
.onAction(this::doActionCloseTransactionsActivated)
.buildAndInstallLocal(this);
} }
@Override @Override
@ -482,6 +507,21 @@ public class TraceRmiConnectionManagerProvider extends ComponentProviderAdapter
} }
} }
private boolean isActionForceCloseTransactionsEnabled(TraceRmiManagerActionContext context) {
TraceRmiManagerNode node = context.getSelectedNode();
if (node instanceof TraceRmiTargetNode) {
return true;
}
return false;
}
private void doActionCloseTransactionsActivated(TraceRmiManagerActionContext context) {
TraceRmiManagerNode node = context.getSelectedNode();
if (node instanceof TraceRmiTargetNode tNode) {
tNode.getTarget().forciblyCloseTransactions();
}
}
@AutoServiceConsumed @AutoServiceConsumed
private void setTraceRmiService(TraceRmiService traceRmiService) { private void setTraceRmiService(TraceRmiService traceRmiService) {
if (this.traceRmiService != null) { if (this.traceRmiService != null) {

View file

@ -950,6 +950,18 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection {
.build(); .build();
} }
protected void checkRestoreEvents(OpenTrace open) {
final boolean restoreEvents;
synchronized (openTxes) {
restoreEvents = openTxes.keySet()
.stream()
.noneMatch(id -> id.doId.equals(open.doId));
}
if (restoreEvents) {
open.trace.setEventsEnabled(true);
}
}
protected ReplyEndTx handleEndTx(RequestEndTx req) { protected ReplyEndTx handleEndTx(RequestEndTx req) {
OpenTx tx; OpenTx tx;
synchronized (openTxes) { synchronized (openTxes) {
@ -973,16 +985,8 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection {
} }
tx.tx.close(); tx.tx.close();
checkRestoreEvents(open);
final boolean restoreEvents;
synchronized (openTxes) {
restoreEvents = openTxes.keySet()
.stream()
.noneMatch(id -> id.doId.domObjId == req.getOid().getId());
}
if (restoreEvents) {
open.trace.setEventsEnabled(true);
}
Swing.runLater( Swing.runLater(
() -> plugin.listeners.invoke().transactionClosed(this, open.target, req.getAbort())); () -> plugin.listeners.invoke().transactionClosed(this, open.target, req.getAbort()));
return ReplyEndTx.getDefaultInstance(); return ReplyEndTx.getDefaultInstance();
@ -1329,7 +1333,9 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection {
@Override @Override
public boolean isBusy() { public boolean isBusy() {
return !openTxes.isEmpty(); synchronized (openTxes) {
return !openTxes.isEmpty();
}
} }
@Override @Override
@ -1339,11 +1345,33 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection {
return false; return false;
} }
for (Tid tid : openTxes.keySet()) { synchronized (openTxes) {
if (Objects.equals(openTrace.doId, tid.doId)) { for (Tid tid : openTxes.keySet()) {
return true; if (Objects.equals(openTrace.doId, tid.doId)) {
return true;
}
} }
} }
return false; return false;
} }
@Override
public void forciblyCloseTransactions(Target target) {
OpenTrace open = openTraces.getByTrace(target.getTrace());
if (open == null || open.target != target) {
return;
}
synchronized (openTxes) {
for (OpenTx tx : List.copyOf(openTxes.values())) {
if (Objects.equals(open.doId, tx.txId.doId)) {
openTxes.remove(tx.txId);
tx.tx.commit();
tx.tx.close();
Swing.runLater(
() -> plugin.listeners.invoke().transactionClosed(this, target, false));
}
}
}
open.trace.setEventsEnabled(true);
}
} }

View file

@ -1716,4 +1716,9 @@ public class TraceRmiTarget extends AbstractTarget {
public boolean isBusy() { public boolean isBusy() {
return connection.isBusy(this); return connection.isBusy(this);
} }
@Override
public void forciblyCloseTransactions() {
connection.forciblyCloseTransactions(this);
}
} }

View file

@ -277,4 +277,8 @@ public abstract class TestTraceRmiConnection extends AbstractTraceRmiConnection
public boolean isBusy(Target target) { public boolean isBusy(Target target) {
return false; return false;
} }
@Override
public void forciblyCloseTransactions(Target target) {
}
} }

View file

@ -136,6 +136,16 @@ public class TargetActionTask extends Task {
/** /**
* Execute an {@link ActionEntry} * Execute an {@link ActionEntry}
* *
* <p>
* If the {@link ProgressService} is available, we will not enforce a timeout, because it should
* be relatively easy for the user to manage the pending tasks. Otherwise, we'll enforce the
* timeout. The rationale here is that some tasks do actually take a good bit of time. For
* example, some targets just have a large module list. Often a GUI component is asking for a
* reason, and if we time it out, that thing doesn't get what it needs. Furthermore, the entry
* disappears from the task list, even though the back-end is likely still working on it. That's
* not good, actually. Since we have a cancel button, let the user decide when it's had enough
* time.
*
* @param tool the tool in which to execute * @param tool the tool in which to execute
* @param title the title, often {@link ActionEntry#display()} * @param title the title, often {@link ActionEntry#display()}
* @param entry the action to execute * @param entry the action to execute
@ -144,11 +154,13 @@ public class TargetActionTask extends Task {
*/ */
public static CompletableFuture<Void> runAction(PluginTool tool, String title, public static CompletableFuture<Void> runAction(PluginTool tool, String title,
ActionEntry entry) { ActionEntry entry) {
return executeTask(tool, new TargetActionTask(tool, title, entry)); return executeTask(tool, new TargetActionTask(tool, title, entry,
tool.getService(ProgressService.class) == null));
} }
private final PluginTool tool; private final PluginTool tool;
private final ActionEntry entry; private final ActionEntry entry;
private final boolean timeout;
/** /**
* Construct a task fore the given action * Construct a task fore the given action
@ -156,17 +168,24 @@ public class TargetActionTask extends Task {
* @param tool the plugin tool * @param tool the plugin tool
* @param title the title, often {@link ActionEntry#display()} * @param title the title, often {@link ActionEntry#display()}
* @param entry the action to execute * @param entry the action to execute
* @param timeout whether or not to enforce the timeout
*/ */
public TargetActionTask(PluginTool tool, String title, ActionEntry entry) { public TargetActionTask(PluginTool tool, String title, ActionEntry entry, boolean timeout) {
super(title, false, false, false); super(title, false, false, false);
this.tool = tool; this.tool = tool;
this.entry = entry; this.entry = entry;
this.timeout = timeout;
} }
@Override @Override
public void run(TaskMonitor monitor) throws CancelledException { public void run(TaskMonitor monitor) throws CancelledException {
try { try {
entry.run(entry.requiresPrompt()); if (timeout) {
entry.run(entry.requiresPrompt());
}
else {
entry.invokeAsyncWithoutTimeout(entry.requiresPrompt()).get();
}
} }
catch (Throwable e) { catch (Throwable e) {
reportError(e); reportError(e);

View file

@ -277,4 +277,8 @@ public class MockTarget implements Target {
public boolean isBusy() { public boolean isBusy() {
return false; return false;
} }
@Override
public void forciblyCloseTransactions() {
}
} }