From 7a1ea5324d8cafbd13f31ba36bbffe988e78dae7 Mon Sep 17 00:00:00 2001 From: Kevin Cooney Date: Sun, 8 Feb 2026 18:08:41 -0800 Subject: [PATCH 1/4] Do not close the temp ntInstance if listener queue cannot be drained --- .../lib2813/preferences/IsolatedPreferences.java | 15 +++++++++++---- .../jupiter/IsolatedNetworkTablesExtension.java | 9 +++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java b/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java index 08acf270..0e69bfb0 100644 --- a/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java +++ b/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java @@ -44,11 +44,18 @@ protected void before() { @Override protected void after() { Preferences.setNetworkTableInstance(prevInstance); - if (!tempInstance.waitForListenerQueue(.6)) { + + // Clear out the listener queue before destroying our temporary NetworkTableInstance. + // + // This works around a race condition in WPILib where a listener registered by Preferences can + // be called after the NetworkTableInstance was closed (see + // https://github.com/wpilibsuite/allwpilib/issues/8215). + if (!tempInstance.waitForListenerQueue(4)) { System.err.println( - "Timed out waiting for the NetworkTableInstance listener queue to empty (waited 600ms);" - + " JVM may crash"); + "Timed out waiting for the NetworkTableInstance listener queue to empty (waited 400ms);" + + " will not close temporary NetworkTableInstance"); + } else { + tempInstance.close(); } - tempInstance.close(); } } diff --git a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtension.java b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtension.java index f7fe1228..ae5c8e37 100644 --- a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtension.java +++ b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtension.java @@ -71,12 +71,13 @@ public void afterEach(ExtensionContext context) { // This works around a race condition in WPILib where a listener registered by Preferences can // be called after the NetworkTableInstance was closed (see // https://github.com/wpilibsuite/allwpilib/issues/8215). - if (!data.testInstance.waitForListenerQueue(.6)) { + if (!data.testInstance.waitForListenerQueue(.4)) { System.err.println( - "Timed out waiting for the NetworkTableInstance listener queue to empty (waited 600ms);" - + " JVM may crash"); + "Timed out waiting for the NetworkTableInstance listener queue to empty (waited 400ms);" + + " will not close temporary NetworkTableInstance"); + } else { + data.testInstance.close(); } - data.testInstance.close(); } } From 748c05a473391a3bc70051ad80ec6701e30734d1 Mon Sep 17 00:00:00 2001 From: Kevin Cooney Date: Sun, 8 Feb 2026 19:42:01 -0800 Subject: [PATCH 2/4] Close the Listener installed by Preferences --- .../preferences/IsolatedPreferences.java | 19 +++++++++++++++++++ .../IsolatedNetworkTablesExtension.java | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java b/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java index 0e69bfb0..f3db3830 100644 --- a/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java +++ b/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java @@ -17,7 +17,9 @@ import edu.wpi.first.networktables.NetworkTable; import edu.wpi.first.networktables.NetworkTableInstance; +import edu.wpi.first.networktables.NetworkTableListener; import edu.wpi.first.wpilibj.Preferences; +import java.lang.reflect.Field; import org.junit.rules.ExternalResource; /** @@ -39,6 +41,7 @@ protected void before() { tempInstance = NetworkTableInstance.create(); tempInstance.startLocal(); Preferences.setNetworkTableInstance(tempInstance); + removePreferencesListener(); } @Override @@ -58,4 +61,20 @@ protected void after() { tempInstance.close(); } } + + /** + * Removes the listener installed by Preferences.setNetworkTableInstance. + * + *

The listener is a constant source of SIGSEGVs in our GitHub test actions. + */ + private static void removePreferencesListener() { + try { + Field listnerField = Preferences.class.getDeclaredField("m_listener"); + listnerField.setAccessible(true); + NetworkTableListener listener = (NetworkTableListener) listnerField.get(null); + listnerField.set(null, null); + listener.close(); + } catch (NoSuchFieldException | IllegalAccessException e) { + } + } } diff --git a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtension.java b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtension.java index ae5c8e37..d64fc7e6 100644 --- a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtension.java +++ b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtension.java @@ -16,7 +16,9 @@ package com.team2813.lib2813.testing.junit.jupiter; import edu.wpi.first.networktables.NetworkTableInstance; +import edu.wpi.first.networktables.NetworkTableListener; import edu.wpi.first.wpilibj.Preferences; +import java.lang.reflect.Field; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.Extension; @@ -56,6 +58,7 @@ public void beforeEach(ExtensionContext context) { NetworkTableInstance ntInstance = DATA_KEY.getOrComputeIfAbsent(store, Data::create).testInstance; Preferences.setNetworkTableInstance(ntInstance); + removePreferencesListener(); } @Override @@ -107,4 +110,20 @@ static Data create() { return new Data(testInstance, prevInstance); } } + + /** + * Removes the listener installed by Preferences.setNetworkTableInstance. + * + *

The listener is a constant source of SIGSEGVs in our GitHub test actions. + */ + private static void removePreferencesListener() { + try { + Field listnerField = Preferences.class.getDeclaredField("m_listener"); + listnerField.setAccessible(true); + NetworkTableListener listener = (NetworkTableListener) listnerField.get(null); + listnerField.set(null, null); + listener.close(); + } catch (NoSuchFieldException | IllegalAccessException e) { + } + } } From 08c9b5ad42d3afd8be9f3b9de44463d7603ea4d4 Mon Sep 17 00:00:00 2001 From: Kevin Cooney Date: Sun, 8 Feb 2026 19:49:03 -0800 Subject: [PATCH 3/4] Remove call to NetworkTableInstance.startLocal() --- .../com/team2813/lib2813/preferences/IsolatedPreferences.java | 1 - .../testing/junit/jupiter/IsolatedNetworkTablesExtension.java | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java b/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java index f3db3830..5a9865e9 100644 --- a/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java +++ b/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java @@ -39,7 +39,6 @@ public NetworkTable getPreferencesTable() { protected void before() { prevInstance = Preferences.getNetworkTable().getInstance(); tempInstance = NetworkTableInstance.create(); - tempInstance.startLocal(); Preferences.setNetworkTableInstance(tempInstance); removePreferencesListener(); } diff --git a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtension.java b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtension.java index d64fc7e6..950a8338 100644 --- a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtension.java +++ b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtension.java @@ -105,7 +105,6 @@ public NetworkTableInstance resolveParameter( private record Data(NetworkTableInstance testInstance, NetworkTableInstance prevInstance) { static Data create() { NetworkTableInstance testInstance = NetworkTableInstance.create(); - testInstance.startLocal(); NetworkTableInstance prevInstance = Preferences.getNetworkTable().getInstance(); return new Data(testInstance, prevInstance); } From fbaab3af2639cad1e5914b446a0d8bad60711331 Mon Sep 17 00:00:00 2001 From: Kevin Cooney Date: Sun, 8 Feb 2026 20:23:54 -0800 Subject: [PATCH 4/4] Update JavaDoc for removePreferencesListener() to use @link --- .../com/team2813/lib2813/preferences/IsolatedPreferences.java | 3 ++- .../testing/junit/jupiter/IsolatedNetworkTablesExtension.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java b/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java index 5a9865e9..cb6ed91b 100644 --- a/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java +++ b/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java @@ -62,7 +62,8 @@ protected void after() { } /** - * Removes the listener installed by Preferences.setNetworkTableInstance. + * Removes the listener installed by {@link + * Preferences#setNetworkTableInstance(NetworkTableInstance)}. * *

The listener is a constant source of SIGSEGVs in our GitHub test actions. */ diff --git a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtension.java b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtension.java index 950a8338..fde01441 100644 --- a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtension.java +++ b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtension.java @@ -111,7 +111,8 @@ static Data create() { } /** - * Removes the listener installed by Preferences.setNetworkTableInstance. + * Removes the listener installed by {@link + * Preferences#setNetworkTableInstance(NetworkTableInstance)}. * *

The listener is a constant source of SIGSEGVs in our GitHub test actions. */