Skip to content

Commit

Permalink
[wpilib] Throw early when EventLoop is modified while running (#6115)
Browse files Browse the repository at this point in the history
  • Loading branch information
ThadHouse authored Jan 1, 2024
1 parent c16946c commit f9aabc5
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 1 deletion.
21 changes: 21 additions & 0 deletions wpilibc/src/main/native/cpp/event/EventLoop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,41 @@

#include "frc/event/EventLoop.h"

#include "frc/Errors.h"

using namespace frc;

namespace {
struct RunningSetter {
bool& m_running;
explicit RunningSetter(bool& running) noexcept : m_running{running} {
m_running = true;
}
~RunningSetter() noexcept { m_running = false; }
};
} // namespace

EventLoop::EventLoop() {}

void EventLoop::Bind(wpi::unique_function<void()> action) {
if (m_running) {
throw FRC_MakeError(err::Error,
"Cannot bind EventLoop while it is running");
}
m_bindings.emplace_back(std::move(action));
}

void EventLoop::Poll() {
RunningSetter runSetter{m_running};
for (wpi::unique_function<void()>& action : m_bindings) {
action();
}
}

void EventLoop::Clear() {
if (m_running) {
throw FRC_MakeError(err::Error,
"Cannot clear EventLoop while it is running");
}
m_bindings.clear();
}
1 change: 1 addition & 0 deletions wpilibc/src/main/native/include/frc/event/EventLoop.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ class EventLoop {

private:
std::vector<wpi::unique_function<void()>> m_bindings;
bool m_running{false};
};
} // namespace frc
24 changes: 24 additions & 0 deletions wpilibc/src/test/native/cpp/event/EventLoopTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.

#include <gtest/gtest.h>

#include "frc/Errors.h"
#include "frc/event/EventLoop.h"

using namespace frc;

TEST(EventLoopTest, ConcurrentModification) {
EventLoop loop;

loop.Bind([&loop] { ASSERT_THROW(loop.Bind([] {}), frc::RuntimeError); });

loop.Poll();

loop.Clear();

loop.Bind([&loop] { ASSERT_THROW(loop.Clear(), frc::RuntimeError); });

loop.Poll();
}
15 changes: 14 additions & 1 deletion wpilibj/src/main/java/edu/wpi/first/wpilibj/event/EventLoop.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,43 @@
package edu.wpi.first.wpilibj.event;

import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.LinkedHashSet;

/**
* A declarative way to bind a set of actions to a loop and execute them when the loop is polled.
*/
public final class EventLoop {
private final Collection<Runnable> m_bindings = new LinkedHashSet<>();
private boolean m_running;

/**
* Bind a new action to run when the loop is polled.
*
* @param action the action to run.
*/
public void bind(Runnable action) {
if (m_running) {
throw new ConcurrentModificationException("Cannot bind EventLoop while it is running");
}
m_bindings.add(action);
}

/** Poll all bindings. */
public void poll() {
m_bindings.forEach(Runnable::run);
try {
m_running = true;
m_bindings.forEach(Runnable::run);
} finally {
m_running = false;
}
}

/** Clear all bindings. */
public void clear() {
if (m_running) {
throw new ConcurrentModificationException("Cannot clear EventLoop while it is running");
}
m_bindings.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
package edu.wpi.first.wpilibj.event;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.util.ConcurrentModificationException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -58,4 +60,33 @@ void testClear() {
// shouldn't change
assertEquals(1, counter.get());
}

@Test
void testConcurrentModification() {
var loop = new EventLoop();

loop.bind(
() -> {
assertThrows(
ConcurrentModificationException.class,
() -> {
loop.bind(() -> {});
});
});

loop.poll();

loop.clear();

loop.bind(
() -> {
assertThrows(
ConcurrentModificationException.class,
() -> {
loop.clear();
});
});

loop.poll();
}
}

0 comments on commit f9aabc5

Please sign in to comment.