Skip to content

Unhandled exception from runOnce command called from a Trigger if it creates further Triggers. #7734

Open
@chauser

Description

@chauser

Describe the bug
A clear and concise description of what the bug is.

Error at frc.robot.TestBehaviors.bindButtons(TestBehaviors.java:85): Unhandled exception: java.util.ConcurrentModificationException: Cannot bind EventLoop while it is running
        at edu.wpi.first.wpilibj.event.EventLoop.bind(EventLoop.java:28)
        at edu.wpi.first.wpilibj2.command.button.Trigger.addBinding(Trigger.java:70)
        at edu.wpi.first.wpilibj2.command.button.Trigger.onTrue(Trigger.java:110)
        at frc.robot.TestBehaviors.bindButtons(TestBehaviors.java:85)
        at edu.wpi.first.wpilibj2.command.FunctionalCommand.initialize(FunctionalCommand.java:52)
        at edu.wpi.first.wpilibj2.command.WrapperCommand.initialize(WrapperCommand.java:38)
        at edu.wpi.first.wpilibj2.command.CommandScheduler.initCommand(CommandScheduler.java:169)
        at edu.wpi.first.wpilibj2.command.CommandScheduler.schedule(CommandScheduler.java:212)
        at edu.wpi.first.wpilibj2.command.CommandScheduler.schedule(CommandScheduler.java:243)
        at edu.wpi.first.wpilibj2.command.Command.schedule(Command.java:538)
        at edu.wpi.first.wpilibj2.command.button.Trigger.lambda$onTrue$1(Trigger.java:113)
        at edu.wpi.first.wpilibj2.command.button.Trigger$1.run(Trigger.java:78)
        at java.base/java.lang.Iterable.forEach(Iterable.java:75)
        at edu.wpi.first.wpilibj.event.EventLoop.poll(EventLoop.java:38)
        at edu.wpi.first.wpilibj2.command.CommandScheduler.run(CommandScheduler.java:280)
        at frc.robot.Robot.robotPeriodic(Robot.java:24)
        at edu.wpi.first.wpilibj.IterativeRobotBase.loopFunc(IterativeRobotBase.java:400)
        at edu.wpi.first.wpilibj.TimedRobot.startCompetition(TimedRobot.java:133)
        at edu.wpi.first.wpilibj.RobotBase.runRobot(RobotBase.java:419)
        at edu.wpi.first.wpilibj.RobotBase.lambda$startRobot$1(RobotBase.java:490)
        at java.base/java.lang.Thread.run(Thread.java:840)

arising from this code:

      ...
      // var bindCommand(Commands.run(this::bindButtons).until(true));
      var bindCommand = Commands.runOnce(this::bindButtons);
      new Trigger(m_testerController.getHID()::isConnected)
              .onTrue(bindCommand.ignoringDisable(true));
      ...

private void bindButtons() {
        if (m_buttonsBound || !m_testerController.getHID().isConnected()) return;
        m_buttonsBound = true;
        // the following line is  frc.robot.TestBehaviors.bindButtons(TestBehaviors.java:85) in the traceback above
        m_testerController.start().onTrue(Commands.runOnce(m_coralSim::loadCoral).withName("LoadSimCoral"));
        m_testerController.rightBumper().whileTrue(new IntakeCommand(m_intake, m_elevator));
}

when m_testerController is connected causing bindButtons() to be run.

Expected behavior
The bindButtons method binds the buttons without incurring an exception.

Desktop (please complete the following information):

  • OS: Linux 22.04 (simulation). The nature of the behavior suggests that it would be the same on the roboRIO or any other desktop.
  • Project Information:
  • WPILib Information:
    Project Version: 2025.2.1
    VS Code Version: 1.94.2
    WPILib Extension Version: 2025.2.1
    C++ Extension Version: 1.23.2
    Java Extension Version: 1.38.0
    Java Debug Extension Version: 0.58.2024090204
    Java Dependencies Extension Version 0.24.1
    Java Version: 17
    Java Location: /home/hauser/wpilib/2025/jdk
    Vendor Libraries:
    PathplannerLib (2025.2.1)
    CTRE-Phoenix (v5) (5.35.1)
    CTRE-Phoenix (v6) (25.2.1)
    REVLib (2025.0.2)
    ReduxLib (2025.0.1)
    Studica (2025.0.1)
    ThriftyLib (2025.0.1)
    WPILib-New-Commands (1.0.0)
    maplesim (0.3.1)
    photonlib (v2025.1.1)
    YAGSL (2025.2.2)

Additional context
For a couple of seasons I've had this as var bindCommand = Commands.run(this::bindButtons). It has worked fine, but this year I noticed that the command was left running after doing its job -- it should have been runOnce. But on changing it to runOnce it suffered the ConcurrentModificationException as shown above.
I think that the problem is that when a Command is scheduled from a Trigger, the Command's start method is called from the Trigger processing loop; and Commands.runOnce places the method to be run as the start method of the Command that it constructs. Using var bindCommand(Commands.run(this::bindButtons).until(true)); instead, the error does not happen because this::bindButtons is placed as the execute method of the constructed command where it is called from the Scheduler loop.
I'm not sure this needs to be fixed -- at this level of the system small changes may well affect other edge-case behaviors. But at least it is now noted and a work-around provided.

Metadata

Metadata

Assignees

No one assigned

    Labels

    type: bugSomething isn't working.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions