Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Throw early when EventLoop is modified while running #6115

Merged
merged 4 commits into from
Jan 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
}
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();
}
}