Skip to content

Commit

Permalink
Fix: Cycle time change from wait for trigger to actual time
Browse files Browse the repository at this point in the history
If the config was changed from `AbstractWorker.ALWAYS_WAIT_FOR_TRIGGER_NEXT_RUN` to a value >= 0 again the cycle thread remaind in a waiting state. The reason for that is that the thread was put in an await state as soon as `AbstractWorker.ALWAYS_WAIT_FOR_TRIGGER_NEXT_RUN` was configured.
This change allows a change at releases the thread again.

Testing: Unit tests, and the simulator app starts up and starts executing cycles
  • Loading branch information
Felix Remmel committed Sep 11, 2024
1 parent b5f385b commit 5ea84da
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 0 deletions.
15 changes: 15 additions & 0 deletions io.openems.common/src/io/openems/common/worker/AbstractWorker.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ public abstract class AbstractWorker {
private final Logger log = LoggerFactory.getLogger(AbstractWorker.class);

private final AtomicBoolean isStopped = new AtomicBoolean(false);
/**
* This value is set to true, in case the thread waits for the trigger of the
* next run, in case cycle time is set to
* {@link AbstractWorker#ALWAYS_WAIT_FOR_TRIGGER_NEXT_RUN}.
*/
private final AtomicBoolean isWaitingForTriggerNextRun = new AtomicBoolean(false);
private final Mutex cycleMutex = new Mutex(false);

/**
Expand Down Expand Up @@ -77,6 +83,9 @@ public void modified(String name, boolean initiallyTriggerNextRun) {
*/
public void modified(String name) {
this.modified(name, true);
if (this.isWaitingForTriggerNextRun.get() && this.getCycleTime() >= 0) {
this.triggerNextRun();
}
}

private void startWorker(String name, boolean autoTriggerNextRun) {
Expand Down Expand Up @@ -144,7 +153,13 @@ public void run() {
}
} else { // < 0 (ALWAYS_WAIT_FOR_TRIGGER_NEXT_RUN)
// wait till next run is triggered
AbstractWorker.this.isWaitingForTriggerNextRun.set(true);
AbstractWorker.this.cycleMutex.await();
AbstractWorker.this.isWaitingForTriggerNextRun.set(false);
if (AbstractWorker.this.getCycleTime() >= 0) {
// cycle time config changed while waiting
continue;
}
}

// store start time
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,21 @@ public SELF activate(AbstractComponentConfig config) throws Exception {
return this.self();
}

public SELF modify(AbstractComponentConfig config) throws Exception {

Check warning on line 545 in io.openems.edge.common/src/io/openems/edge/common/test/AbstractComponentTest.java

View workflow job for this annotation

GitHub Actions / build-java

Missing a Javadoc comment.

// Add the configuration to ConfigurationAdmin
for (Object object : this.references) {
if (object instanceof DummyConfigurationAdmin) {
var cm = (DummyConfigurationAdmin) object;
cm.addConfig(config);
}
}

this.callModified(config);

return this.self();
}

/**
* Calls the 'deactivate()' method of the 'system-under-test'.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,8 @@ public int getCycleTime() {
return Cycle.DEFAULT_CYCLE_TIME;
}

public void triggerNextCycle() {
this.worker.triggerNextRun();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package io.openems.edge.core.cycle;

import static org.junit.Assert.assertEquals;

import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.concurrent.atomic.AtomicInteger;

import org.junit.Test;

import io.openems.common.worker.AbstractWorker;
import io.openems.edge.common.component.AbstractOpenemsComponent;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.common.sum.DummySum;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.ComponentTest;
import io.openems.edge.common.test.DummyComponentManager;
import io.openems.edge.common.test.DummyConfigurationAdmin;
import io.openems.edge.common.test.DummyEventAdmin;
import io.openems.edge.controller.test.DummyController;
import io.openems.edge.scheduler.api.Scheduler;

public class CycleImplTest {

private final class SchedulerImplementation extends AbstractOpenemsComponent implements Scheduler {

private LinkedHashSet<String> controllers;

protected SchedulerImplementation(LinkedHashSet<String> controllers) {
super(OpenemsComponent.ChannelId.values(), Scheduler.ChannelId.values());
this.controllers = controllers;
}

@Override
public LinkedHashSet<String> getControllers() {
return controllers;
}

@Override
public String id() {
return "Bla";
}
}

@Test
public void testChangeCycleTime() throws Exception {

var dummyController = new DummyController("BlaDummyController");
var eventAdmin = new DummyEventAdmin();
var controllerExecutionCount = new AtomicInteger();
var newValueForCycle = new AtomicInteger();
dummyController.setRunCallback(() -> {
controllerExecutionCount.set(newValueForCycle.get());
});

var scheduler = new SchedulerImplementation(
new LinkedHashSet<String>(Collections.singleton(dummyController.id())));

var sut = new CycleImpl();
new ComponentTest(sut) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager()) //
.addReference("sumComponent", new DummySum()) //
.addReference("addScheduler", scheduler) //
.addReference("eventAdmin", eventAdmin) //
.addComponent(dummyController) //
.activate(MyConfig.create().cycleTime(AbstractWorker.DO_NOT_WAIT).build()) //
.next(new TestCase("Run cycle with 0 cycle time") //
.onAfterControllersCallbacks(() -> {
System.out.println("Test 1");
newValueForCycle.getAndIncrement();
Thread.sleep(10); // required, because cycle does not have an external clock stopwatch
assertEquals(newValueForCycle.get(), controllerExecutionCount.get()); //
System.out.println("Done");
})) //
.modify(MyConfig.create().cycleTime(AbstractWorker.ALWAYS_WAIT_FOR_TRIGGER_NEXT_RUN).build()) //
.next(new TestCase("No new cycle executes, as next run must be triggered manually") //
.onAfterControllersCallbacks(() -> {
System.out.println("Test 2");
newValueForCycle.getAndIncrement();
Thread.sleep(10); // required, because cycle does not have an external clock stopwatch
assertEquals(newValueForCycle.get() - 1, controllerExecutionCount.get()); //
System.out.println("Done");
})) //
.next(new TestCase("New cycle execution manually triggered") //
.onAfterControllersCallbacks(() -> {
System.out.println("Test 3");
sut.triggerNextCycle();
Thread.sleep(10); // required, because cycle does not have an external clock stopwatch
assertEquals(newValueForCycle.get(), controllerExecutionCount.get());
System.out.println("Done");
}))
.modify(MyConfig.create().cycleTime(20).build()) //
.next(new TestCase("Set wait time again, but do not wait") //
.onAfterControllersCallbacks(() -> {
System.out.println("Test 4");
newValueForCycle.getAndIncrement();
Thread.sleep(10); // required, because cycle does not have an external clock stopwatch
assertEquals(newValueForCycle.get() - 1, controllerExecutionCount.get());
System.out.println("Done");
}))
.next(new TestCase("Wait until cycle time passed") //
.onAfterControllersCallbacks(() -> {
System.out.println("Test 5");
Thread.sleep(20); // required, because cycle does not have an external clock stopwatch
assertEquals(newValueForCycle.get(), controllerExecutionCount.get());
System.out.println("Done");
})); //; //

}

}
45 changes: 45 additions & 0 deletions io.openems.edge.core/test/io/openems/edge/core/cycle/MyConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package io.openems.edge.core.cycle;

import io.openems.common.test.AbstractComponentConfig;

@SuppressWarnings("all")
public class MyConfig extends AbstractComponentConfig implements Config {

protected static class Builder {

private int cycleTime;

private Builder() {
}

public MyConfig build() {
return new MyConfig(this);
}

public Builder cycleTime(int cycleTime) {
this.cycleTime = cycleTime;
return this;
}
}

/**
* Create a Config builder.
*
* @return a {@link Builder}
*/
public static Builder create() {
return new Builder();
}

private final Builder builder;

private MyConfig(Builder builder) {
super(Config.class, CycleImpl.SINGLETON_COMPONENT_ID);
this.builder = builder;
}

@Override
public int cycleTime() {
return builder.cycleTime;
}
}

0 comments on commit 5ea84da

Please sign in to comment.