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

Fix: Cycle time change from wait for trigger to actual time #2790

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
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
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 @@ -75,6 +81,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 synchronized void startWorker(String name, boolean autoTriggerNextRun) {
Expand Down Expand Up @@ -143,7 +152,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 @@ -724,6 +724,28 @@ public SELF activate(AbstractComponentConfig config) throws Exception {
return this.self();
}

/**
* Calls the 'modify()' method of the 'system-under-test'.
*
* @param config the configuration
* @return itself, to use as a builder
* @throws Exception on error
*/
public SELF modify(AbstractComponentConfig config) throws Exception {

// 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,11 @@ public int getCycleTime() {
return Cycle.DEFAULT_CYCLE_TIME;
}

/**
* Triggers the execution of the next cycle.
*/
public void triggerNextCycle() {
this.worker.triggerNextRun();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
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 this.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(() -> {
newValueForCycle.getAndIncrement();
Thread.sleep(20); // required, because cycle does not have an external clock stopwatch
assertEquals(newValueForCycle.get(), controllerExecutionCount.get()); //
})) //
.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(() -> {
newValueForCycle.getAndIncrement();
Thread.sleep(20); // required, because cycle does not have an external clock stopwatch
assertEquals(newValueForCycle.get() - 1, controllerExecutionCount.get()); //
})) //
.next(new TestCase("New cycle execution manually triggered") //
.onAfterControllersCallbacks(() -> {
sut.triggerNextCycle();
Thread.sleep(20); // 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(() -> {
newValueForCycle.getAndIncrement();
assertEquals(newValueForCycle.get() - 1, controllerExecutionCount.get());
}))
.next(new TestCase("Wait until cycle time passed") //
.onAfterControllersCallbacks(() -> {
Thread.sleep(50); // required, because cycle does not have an external clock stopwatch
assertEquals(newValueForCycle.get(), controllerExecutionCount.get());
}))
.modify(MyConfig.create().cycleTime(5000).build()) //
.next(new TestCase("Set wait time again, but do not wait") //
.onAfterControllersCallbacks(() -> {
newValueForCycle.getAndIncrement();
// cycle is not passed yet
assertEquals(newValueForCycle.get() - 1, controllerExecutionCount.get());
}))
.modify(MyConfig.create().cycleTime(20).build()) //
.next(new TestCase("Set wait time again before previous cycle completed") //
.onAfterControllersCallbacks(() -> {
newValueForCycle.getAndIncrement();
Thread.sleep(100); // required, because cycle does not have an external clock stopwatch
assertEquals(newValueForCycle.get(), controllerExecutionCount.get());
}));

}

}
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 this.builder.cycleTime;
}
}
Loading