feat(ft546): run standard WCS as the single scheduler
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
package com.ruoyi.project.ft546.runtime;
|
||||
|
||||
import com.ruoyi.wcs.application.WcsRuntimeCoordinator;
|
||||
import com.ruoyi.wcs.application.device.CommandOutboxDispatcher;
|
||||
import com.ruoyi.wcs.application.device.DeviceExecutionService;
|
||||
import com.ruoyi.wcs.application.device.SnapshotCollector;
|
||||
import com.ruoyi.wcs.application.dispatch.DispatchCoordinator;
|
||||
import com.ruoyi.wcs.application.recovery.RecoveryGate;
|
||||
import com.ruoyi.wcs.domain.shared.WcsClock;
|
||||
import com.ruoyi.wcs.port.out.DispatchSnapshotProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnProperty(prefix = "ft546.wcs", name = "runtime-mode",
|
||||
havingValue = "STANDARD")
|
||||
public class Ft546AdapterConfiguration {
|
||||
private static final int OUTBOX_BATCH_LIMIT = 100;
|
||||
|
||||
@Bean
|
||||
public WcsRuntimeCoordinator ft546WcsRuntimeCoordinator(
|
||||
SnapshotCollector snapshotCollector,
|
||||
RecoveryGate recoveryGate,
|
||||
DispatchSnapshotProvider snapshotProvider,
|
||||
DispatchCoordinator dispatchCoordinator,
|
||||
DeviceExecutionService executionService,
|
||||
CommandOutboxDispatcher outboxDispatcher,
|
||||
WcsClock clock) {
|
||||
return new WcsRuntimeCoordinator(snapshotCollector, recoveryGate,
|
||||
snapshotProvider, dispatchCoordinator, executionService,
|
||||
outboxDispatcher, clock, OUTBOX_BATCH_LIMIT);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Ft546WcsScheduler ft546WcsScheduler(
|
||||
WcsRuntimeCoordinator coordinator) {
|
||||
return new Ft546WcsScheduler(coordinator);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.ruoyi.project.ft546.runtime;
|
||||
|
||||
import com.ruoyi.wcs.application.RuntimeTickResult;
|
||||
import com.ruoyi.wcs.application.WcsRuntimeCoordinator;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public final class Ft546WcsScheduler {
|
||||
private final Supplier<RuntimeTickResult> tick;
|
||||
private final AtomicBoolean tickInProgress = new AtomicBoolean();
|
||||
private final AtomicLong completedTickCount = new AtomicLong();
|
||||
private volatile RuntimeTickResult lastResult;
|
||||
|
||||
public Ft546WcsScheduler(WcsRuntimeCoordinator coordinator) {
|
||||
this(Objects.requireNonNull(coordinator, "coordinator")::tick);
|
||||
}
|
||||
|
||||
Ft546WcsScheduler(Supplier<RuntimeTickResult> tick) {
|
||||
this.tick = Objects.requireNonNull(tick, "tick");
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelayString =
|
||||
"${ft546.wcs.dispatch-interval-ms:1000}")
|
||||
public void runScheduledTick() {
|
||||
if (!tickInProgress.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
lastResult = Objects.requireNonNull(tick.get(),
|
||||
"runtime coordinator returned null");
|
||||
completedTickCount.incrementAndGet();
|
||||
} finally {
|
||||
tickInProgress.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
public long getCompletedTickCount() {
|
||||
return completedTickCount.get();
|
||||
}
|
||||
|
||||
public RuntimeTickResult getLastResult() {
|
||||
return lastResult;
|
||||
}
|
||||
|
||||
public boolean isTickInProgress() {
|
||||
return tickInProgress.get();
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import com.ruoyi.device.tsj.domain.DeviceTsj;
|
||||
import com.ruoyi.device.tsj.service.IDeviceTsjService;
|
||||
import com.ruoyi.project.ft546.device.Ft546DeviceRuntimeRegistry;
|
||||
import com.ruoyi.project.ft546.device.shuttle.Ft546SrgvRuntimeFactory;
|
||||
import com.ruoyi.project.ft546.config.Ft546WcsProperties;
|
||||
import com.ruoyi.schedule.plcRead.cdz.PlcCdzRead;
|
||||
import com.ruoyi.schedule.plcRead.ddj.PlcDdjRead;
|
||||
import com.ruoyi.schedule.plcRead.srgv.PlcSrgvRead;
|
||||
@@ -27,6 +28,8 @@ import com.ruoyi.schedule.scheduleTask.ssj.SsjThread;
|
||||
import com.ruoyi.schedule.scheduleTask.tsj.TsjThread;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
@@ -66,6 +69,8 @@ public class ScheduleThread {
|
||||
private Ft546DeviceRuntimeRegistry deviceRuntimeRegistry;
|
||||
@Autowired
|
||||
private Ft546SrgvRuntimeFactory srgvRuntimeFactory;
|
||||
@Autowired
|
||||
private Ft546WcsProperties ft546WcsProperties;
|
||||
|
||||
private volatile boolean isRun = false;
|
||||
private volatile boolean isRun2 = false;
|
||||
@@ -90,10 +95,20 @@ public class ScheduleThread {
|
||||
|
||||
}
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void startStandardFactReadersAfterApplicationReady() {
|
||||
if (isStandardMode()) {
|
||||
startAllThread();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调度初始化
|
||||
*/
|
||||
private void scheduleInit() {
|
||||
if (isStandardMode()) {
|
||||
return;
|
||||
}
|
||||
MonitoringThread monitoringThread = new MonitoringThread();
|
||||
monitoringCustomThread = new CustomThread<MonitoringThread>(new Thread(monitoringThread), monitoringThread);
|
||||
SsjThread ssjThread=new SsjThread();
|
||||
@@ -196,6 +211,10 @@ public class ScheduleThread {
|
||||
* 开启线程2
|
||||
*/
|
||||
public void startAllThread2() {
|
||||
if (isStandardMode()) {
|
||||
throw new IllegalStateException(
|
||||
"legacy business scheduler is disabled in STANDARD mode");
|
||||
}
|
||||
monitoringCustomThread.thread.start();
|
||||
ssjThreadCustomThread.thread.start();
|
||||
tsjThreadCustomThread.thread.start();
|
||||
@@ -322,6 +341,10 @@ public class ScheduleThread {
|
||||
* 停止线程2
|
||||
*/
|
||||
public void stopAllThread2() {
|
||||
if (isStandardMode()) {
|
||||
isRun2 = false;
|
||||
return;
|
||||
}
|
||||
monitoringCustomThread.runnable.isRun = false;
|
||||
// ddj1CustomThread.runnable.isRun = false;
|
||||
/*ddj2CustomThread.runnable.isRun = false;
|
||||
@@ -342,4 +365,10 @@ public class ScheduleThread {
|
||||
map.put("isRun2",isRun2);
|
||||
return map;
|
||||
}
|
||||
|
||||
private boolean isStandardMode() {
|
||||
return ft546WcsProperties != null
|
||||
&& ft546WcsProperties.getRuntimeMode()
|
||||
== Ft546WcsProperties.RuntimeMode.STANDARD;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.ruoyi.job.service.impl.JobCallBackServiceImpl;
|
||||
import com.ruoyi.queue.srgv.domain.WcsSrgvQueue;
|
||||
import com.ruoyi.queue.srgv.service.IWcsSrgvQueueFinishService;
|
||||
import com.ruoyi.queue.srgv.service.IWcsSrgvQueueService;
|
||||
import com.ruoyi.project.ft546.config.Ft546WcsProperties;
|
||||
import com.ruoyi.project.ft546.device.Ft546DeviceRuntimeRegistry;
|
||||
import com.ruoyi.schedule.plcRead.srgv.PlcSrgvRead;
|
||||
import com.ruoyi.schedule.plcRead.srgv.roadUtil.PathFinder;
|
||||
@@ -94,6 +95,10 @@ public class BaseSRgvThread implements Runnable, SRgvThreadInterface {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (isStandardRuntime()) {
|
||||
log.warn("STANDARD mode owns scheduling; legacy SRGV thread will not run");
|
||||
return;
|
||||
}
|
||||
|
||||
init();
|
||||
while (isRun) {
|
||||
@@ -136,6 +141,13 @@ public class BaseSRgvThread implements Runnable, SRgvThreadInterface {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isStandardRuntime() {
|
||||
Ft546WcsProperties properties =
|
||||
SpringUtils.getBean(Ft546WcsProperties.class);
|
||||
return properties.getRuntimeMode()
|
||||
== Ft546WcsProperties.RuntimeMode.STANDARD;
|
||||
}
|
||||
|
||||
/**
|
||||
* 离开电桩
|
||||
*
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
package com.ruoyi.project.ft546.runtime;
|
||||
|
||||
import com.ruoyi.project.ft546.config.Ft546WcsProperties;
|
||||
import com.ruoyi.common.utils.spring.SpringUtils;
|
||||
import com.ruoyi.schedule.ScheduleThread;
|
||||
import com.ruoyi.schedule.scheduleTask.srgv.BaseSRgvThread;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class Ft546StandardRuntimeTest {
|
||||
|
||||
@Test
|
||||
void standardModeDoesNotConstructLegacyBusinessThreads() {
|
||||
ScheduleThread runtime = runtime(Ft546WcsProperties.RuntimeMode.STANDARD);
|
||||
|
||||
ReflectionTestUtils.invokeMethod(runtime, "scheduleInit");
|
||||
|
||||
assertNull(runtime.ssjThreadCustomThread);
|
||||
assertNull(runtime.sRgvThreadCustomThread);
|
||||
assertNull(runtime.tsjThreadCustomThread);
|
||||
assertNull(runtime.cdzThreadCustomThread);
|
||||
}
|
||||
|
||||
@Test
|
||||
void legacyModeKeepsExistingBusinessThreadInitialization() {
|
||||
ScheduleThread runtime = runtime(Ft546WcsProperties.RuntimeMode.LEGACY);
|
||||
|
||||
ReflectionTestUtils.invokeMethod(runtime, "scheduleInit");
|
||||
|
||||
assertNotNull(runtime.ssjThreadCustomThread);
|
||||
assertNotNull(runtime.sRgvThreadCustomThread);
|
||||
assertNotNull(runtime.tsjThreadCustomThread);
|
||||
assertNotNull(runtime.cdzThreadCustomThread);
|
||||
}
|
||||
|
||||
@Test
|
||||
void standardModeRejectsManualLegacySchedulerStart() {
|
||||
ScheduleThread runtime = runtime(Ft546WcsProperties.RuntimeMode.STANDARD);
|
||||
|
||||
IllegalStateException failure = assertThrows(IllegalStateException.class,
|
||||
runtime::startAllThread2);
|
||||
|
||||
assertEquals("legacy business scheduler is disabled in STANDARD mode",
|
||||
failure.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void baseSrgvThreadRefusesToRunInStandardMode() {
|
||||
Object originalFactory = ReflectionTestUtils.getField(
|
||||
SpringUtils.class, "beanFactory");
|
||||
DefaultListableBeanFactory factory =
|
||||
new DefaultListableBeanFactory();
|
||||
Ft546WcsProperties properties = new Ft546WcsProperties();
|
||||
properties.setRuntimeMode(Ft546WcsProperties.RuntimeMode.STANDARD);
|
||||
factory.registerSingleton("ft546WcsProperties", properties);
|
||||
ReflectionTestUtils.setField(SpringUtils.class, "beanFactory",
|
||||
factory);
|
||||
AtomicBoolean initialized = new AtomicBoolean();
|
||||
BaseSRgvThread legacyThread = new BaseSRgvThread() {
|
||||
@Override
|
||||
public void init() {
|
||||
initialized.set(true);
|
||||
isRun = false;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
legacyThread.run();
|
||||
} finally {
|
||||
ReflectionTestUtils.setField(SpringUtils.class, "beanFactory",
|
||||
originalFactory);
|
||||
}
|
||||
|
||||
assertFalse(initialized.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void runtimeModeDefaultsToLegacy() {
|
||||
assertEquals(Ft546WcsProperties.RuntimeMode.LEGACY,
|
||||
new Ft546WcsProperties().getRuntimeMode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void standardSchedulerOwnsTheCoreTick() {
|
||||
AtomicInteger ticks = new AtomicInteger();
|
||||
Ft546WcsScheduler scheduler = new Ft546WcsScheduler(() -> {
|
||||
ticks.incrementAndGet();
|
||||
return com.ruoyi.wcs.application.RuntimeTickResult
|
||||
.recoveryBlocked();
|
||||
});
|
||||
|
||||
scheduler.runScheduledTick();
|
||||
|
||||
assertEquals(1, ticks.get());
|
||||
assertEquals(1L, scheduler.getCompletedTickCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void legacyModeDoesNotRequireStandardRuntimePorts() {
|
||||
new ApplicationContextRunner()
|
||||
.withUserConfiguration(Ft546AdapterConfiguration.class)
|
||||
.withPropertyValues("ft546.wcs.runtime-mode=LEGACY")
|
||||
.run(context -> {
|
||||
assertNull(context.getStartupFailure());
|
||||
assertTrue(context.getBeansOfType(
|
||||
Ft546WcsScheduler.class).isEmpty());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void standardModeFailsFastWhenPersistentRuntimePortsAreMissing() {
|
||||
new ApplicationContextRunner()
|
||||
.withUserConfiguration(Ft546AdapterConfiguration.class)
|
||||
.withPropertyValues("ft546.wcs.runtime-mode=STANDARD")
|
||||
.run(context -> assertNotNull(context.getStartupFailure()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void standardFactReadersStartOnlyAfterApplicationIsReady() {
|
||||
AtomicInteger starts = new AtomicInteger();
|
||||
ScheduleThread runtime = new ScheduleThread() {
|
||||
@Override
|
||||
public synchronized void startAllThread() {
|
||||
starts.incrementAndGet();
|
||||
}
|
||||
};
|
||||
Ft546WcsProperties properties = new Ft546WcsProperties();
|
||||
properties.setRuntimeMode(Ft546WcsProperties.RuntimeMode.STANDARD);
|
||||
ReflectionTestUtils.setField(runtime, "ft546WcsProperties",
|
||||
properties);
|
||||
|
||||
runtime.startStandardFactReadersAfterApplicationReady();
|
||||
|
||||
assertEquals(1, starts.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void legacyFactReadersRemainOperatorControlledAfterApplicationReady() {
|
||||
AtomicInteger starts = new AtomicInteger();
|
||||
ScheduleThread runtime = new ScheduleThread() {
|
||||
@Override
|
||||
public synchronized void startAllThread() {
|
||||
starts.incrementAndGet();
|
||||
}
|
||||
};
|
||||
ReflectionTestUtils.setField(runtime, "ft546WcsProperties",
|
||||
new Ft546WcsProperties());
|
||||
|
||||
runtime.startStandardFactReadersAfterApplicationReady();
|
||||
|
||||
assertEquals(0, starts.get());
|
||||
}
|
||||
|
||||
private static ScheduleThread runtime(
|
||||
Ft546WcsProperties.RuntimeMode mode) {
|
||||
Ft546WcsProperties properties = new Ft546WcsProperties();
|
||||
properties.setRuntimeMode(mode);
|
||||
ScheduleThread runtime = new ScheduleThread();
|
||||
ReflectionTestUtils.setField(runtime, "ft546WcsProperties",
|
||||
properties);
|
||||
return runtime;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user