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..cb6ed91b 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; /** @@ -37,18 +39,42 @@ public NetworkTable getPreferencesTable() { protected void before() { prevInstance = Preferences.getNetworkTable().getInstance(); tempInstance = NetworkTableInstance.create(); - tempInstance.startLocal(); Preferences.setNetworkTableInstance(tempInstance); + removePreferencesListener(); } @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(); + } + } + + /** + * Removes the listener installed by {@link + * Preferences#setNetworkTableInstance(NetworkTableInstance)}. + * + *

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) { } - 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..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 @@ -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 @@ -71,12 +74,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(); } } @@ -101,9 +105,25 @@ 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); } } + + /** + * Removes the listener installed by {@link + * Preferences#setNetworkTableInstance(NetworkTableInstance)}. + * + *

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) { + } + } }