This is an automated email from the ASF dual-hosted git repository.

mattcasters pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 1a301fdab6 Avoid SWT Invalid thread access on GetFields from lineage, 
fixes #7896 (#7897)
1a301fdab6 is described below

commit 1a301fdab63965e38cf2fc7d1b050c86671851d1
Author: Matt Casters <[email protected]>
AuthorDate: Tue Aug 11 21:54:56 2026 +0200

    Avoid SWT Invalid thread access on GetFields from lineage, fixes #7896 
(#7897)
    
    Skip unit-test state lookup when not on the UI thread so transform-thread
    GetFieldsExtension calls no longer touch SWT. Broaden design-graph lineage
    fallback exception handling.
---
 .../hop/lineage/LineageTransformSchemaEmitter.java |  8 +++--
 .../apache/hop/testing/gui/TestingGuiPlugin.java   | 42 +++++++++++++++-------
 .../hop/testing/gui/TestingGuiPluginTweakTest.java | 10 ++++++
 3 files changed, 45 insertions(+), 15 deletions(-)

diff --git 
a/engine/src/main/java/org/apache/hop/lineage/LineageTransformSchemaEmitter.java
 
b/engine/src/main/java/org/apache/hop/lineage/LineageTransformSchemaEmitter.java
index 5c0d3a62ec..fccfe50d34 100644
--- 
a/engine/src/main/java/org/apache/hop/lineage/LineageTransformSchemaEmitter.java
+++ 
b/engine/src/main/java/org/apache/hop/lineage/LineageTransformSchemaEmitter.java
@@ -20,7 +20,6 @@ package org.apache.hop.lineage;
 import java.util.ArrayList;
 import java.util.List;
 import org.apache.hop.core.IRowSet;
-import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.row.IRowMeta;
 import org.apache.hop.core.row.IValueMeta;
 import org.apache.hop.core.row.value.ValueMetaFactory;
@@ -200,7 +199,9 @@ public final class LineageTransformSchemaEmitter {
     }
     try {
       return pipelineMeta.getPrevTransformFields(pipeline, transformMeta);
-    } catch (HopException ignored) {
+    } catch (Exception ignored) {
+      // Includes HopException and unexpected runtime failures from GetFields 
extension points
+      // (e.g. SWT access from a worker thread); design-graph schema is 
best-effort only.
       return null;
     }
   }
@@ -233,7 +234,8 @@ public final class LineageTransformSchemaEmitter {
     }
     try {
       return pipelineMeta.getTransformFields(pipeline, transformMeta);
-    } catch (HopException ignored) {
+    } catch (Exception ignored) {
+      // Best-effort design-graph fallback; see resolveInputRowMetaFromGraph.
       return null;
     }
   }
diff --git 
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/gui/TestingGuiPlugin.java
 
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/gui/TestingGuiPlugin.java
index 16ba893f38..ee4ca13e1d 100644
--- 
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/gui/TestingGuiPlugin.java
+++ 
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/gui/TestingGuiPlugin.java
@@ -87,8 +87,10 @@ import org.apache.hop.ui.testing.EditRowsDialog;
 import org.apache.hop.workflow.WorkflowMeta;
 import org.apache.hop.workflow.action.ActionMeta;
 import org.eclipse.swt.SWT;
+import org.eclipse.swt.SWTException;
 import org.eclipse.swt.widgets.Combo;
 import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.swt.widgets.TableItem;
 
@@ -1344,26 +1346,38 @@ public class TestingGuiPlugin {
    * <p>Prefers the active pipeline graph when it matches, then scans explorer 
tabs. Matching is by
    * identity first, then {@link PipelineMeta#equals(Object)} (filename/name).
    *
+   * <p>Must only be called from the UI thread: looking up the active tab 
touches SWT widgets.
+   * Background callers (e.g. lineage GetFields from a transform thread) get 
{@code null}.
+   *
    * @param pipelineMeta the pipeline metadata
-   * @return the graph, or null when the pipeline is not open in the GUI
+   * @return the graph, or null when the pipeline is not open in the GUI or 
the caller is not on the
+   *     UI thread
    */
   public static HopGuiPipelineGraph getPipelineGraph(PipelineMeta 
pipelineMeta) {
-    HopGuiPipelineGraph active = HopGui.getActivePipelineGraph();
-    if (active != null && pipelineMetaMatches(active.getPipelineMeta(), 
pipelineMeta)) {
-      return active;
-    }
-    if (pipelineMeta == null || HopGui.getExplorerPerspective() == null) {
+    // Tab / graph lookup may touch SWT widgets; only safe on the UI thread 
(issue #7896).
+    if (Display.getCurrent() == null) {
       return null;
     }
-    for (TabItemHandler item : HopGui.getExplorerPerspective().getItems()) {
-      if (!(item.getTypeHandler() instanceof HopGuiPipelineGraph 
pipelineGraph)) {
-        continue;
+    try {
+      HopGuiPipelineGraph active = HopGui.getActivePipelineGraph();
+      if (active != null && pipelineMetaMatches(active.getPipelineMeta(), 
pipelineMeta)) {
+        return active;
       }
-      if (pipelineMetaMatches(pipelineGraph.getPipelineMeta(), pipelineMeta)) {
-        return pipelineGraph;
+      if (pipelineMeta == null || HopGui.getExplorerPerspective() == null) {
+        return null;
+      }
+      for (TabItemHandler item : HopGui.getExplorerPerspective().getItems()) {
+        if (!(item.getTypeHandler() instanceof HopGuiPipelineGraph 
pipelineGraph)) {
+          continue;
+        }
+        if (pipelineMetaMatches(pipelineGraph.getPipelineMeta(), 
pipelineMeta)) {
+          return pipelineGraph;
+        }
       }
+      return null;
+    } catch (SWTException e) {
+      return null;
     }
-    return null;
   }
 
   private static boolean pipelineMetaMatches(PipelineMeta a, PipelineMeta b) {
@@ -1379,6 +1393,10 @@ public class TestingGuiPlugin {
     if (!"GUI".equalsIgnoreCase(Const.getHopPlatformRuntime())) {
       return null;
     }
+    // Same rule as getPipelineGraph: never access HopGui/SWT from a worker 
thread (issue #7896).
+    if (Display.getCurrent() == null) {
+      return null;
+    }
     Map<String, Object> stateMap = getStateMap(pipelineMeta);
     if (stateMap == null) {
       return null;
diff --git 
a/plugins/misc/testing/src/test/java/org/apache/hop/testing/gui/TestingGuiPluginTweakTest.java
 
b/plugins/misc/testing/src/test/java/org/apache/hop/testing/gui/TestingGuiPluginTweakTest.java
index 33f0136f88..64da9ee26d 100644
--- 
a/plugins/misc/testing/src/test/java/org/apache/hop/testing/gui/TestingGuiPluginTweakTest.java
+++ 
b/plugins/misc/testing/src/test/java/org/apache/hop/testing/gui/TestingGuiPluginTweakTest.java
@@ -77,6 +77,16 @@ class TestingGuiPluginTweakTest {
     assertTrue(targets.isEmpty());
   }
 
+  /**
+   * getCurrentUnitTest must not touch SWT from a non-UI thread (issue #7896). 
Unit tests have no
+   * Display, so Display.getCurrent() is null and the method returns null 
without throwing.
+   */
+  @Test
+  void getCurrentUnitTestReturnsNullOffUiThread() {
+    assertNull(TestingGuiPlugin.getCurrentUnitTest(new PipelineMeta()));
+    assertNull(TestingGuiPlugin.getStateMap(new PipelineMeta()));
+  }
+
   @Test
   void applyTweakEnableAddsBypass() {
     PipelineUnitTest unitTest = new PipelineUnitTest();

Reply via email to