fix(ft546): serialize shuttle reader lifecycle
This commit is contained in:
@@ -38,6 +38,9 @@ public class Ft546SrgvRuntimeFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public PlcSrgvRead create(DeviceSrgv device) {
|
public PlcSrgvRead create(DeviceSrgv device) {
|
||||||
|
if (device == null) {
|
||||||
|
throw new IllegalArgumentException("device identity is required");
|
||||||
|
}
|
||||||
Ft546DeviceProfile profile = new Ft546DeviceProfile();
|
Ft546DeviceProfile profile = new Ft546DeviceProfile();
|
||||||
profile.setDeviceId(String.valueOf(device.getDeviceId()));
|
profile.setDeviceId(String.valueOf(device.getDeviceId()));
|
||||||
profile.setDeviceType("SHUTTLE");
|
profile.setDeviceType("SHUTTLE");
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -66,8 +67,10 @@ public class ScheduleThread {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private Ft546SrgvRuntimeFactory srgvRuntimeFactory;
|
private Ft546SrgvRuntimeFactory srgvRuntimeFactory;
|
||||||
|
|
||||||
private boolean isRun = false;
|
private volatile boolean isRun = false;
|
||||||
private boolean isRun2 = false;
|
private volatile boolean isRun2 = false;
|
||||||
|
|
||||||
|
private static final long THREAD_STOP_TIMEOUT_MS = 5000L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 项目初始化启动
|
* 项目初始化启动
|
||||||
@@ -171,16 +174,21 @@ public class ScheduleThread {
|
|||||||
/**
|
/**
|
||||||
* 开始线程
|
* 开始线程
|
||||||
*/
|
*/
|
||||||
public void startAllThread() {
|
public synchronized void startAllThread() {
|
||||||
plcSsjReadCustomThread.getThread().start();
|
if (isRun) {
|
||||||
for (CustomThread<PlcSrgvRead> reader
|
return;
|
||||||
: plcSrgvReadCustomThreads.values()) {
|
}
|
||||||
reader.getThread().start();
|
List<Thread> readerThreads = requireFactReaderThreads();
|
||||||
|
for (Thread readerThread : readerThreads) {
|
||||||
|
if (readerThread.getState() != Thread.State.NEW) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"PLC fact reader cannot be restarted: "
|
||||||
|
+ readerThread.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Thread readerThread : readerThreads) {
|
||||||
|
readerThread.start();
|
||||||
}
|
}
|
||||||
plcTsjReadCustomThread.getThread().start();
|
|
||||||
plcCdzReadCustomThread.getThread().start();
|
|
||||||
|
|
||||||
|
|
||||||
isRun = true;
|
isRun = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,14 +208,18 @@ public class ScheduleThread {
|
|||||||
/**
|
/**
|
||||||
* 停止线程
|
* 停止线程
|
||||||
*/
|
*/
|
||||||
public void stopAllThread() {
|
public synchronized void stopAllThread() {
|
||||||
plcSsjReadCustomThread.runnable.isRun = false;
|
if (!isRun) {
|
||||||
for (CustomThread<PlcSrgvRead> reader
|
return;
|
||||||
: plcSrgvReadCustomThreads.values()) {
|
|
||||||
reader.runnable.isRun = false;
|
|
||||||
}
|
}
|
||||||
|
List<Thread> readerThreads = currentFactReaderThreads();
|
||||||
|
signalFactReadersToStop();
|
||||||
|
isRun = false;
|
||||||
|
awaitFactReadersStopped(readerThreads);
|
||||||
plcSrgvReadCustomThreads.clear();
|
plcSrgvReadCustomThreads.clear();
|
||||||
deviceRuntimeRegistry.clear();
|
if (deviceRuntimeRegistry != null) {
|
||||||
|
deviceRuntimeRegistry.clear();
|
||||||
|
}
|
||||||
// plcDdj1ReadCustomThread.runnable.isRun = false;
|
// plcDdj1ReadCustomThread.runnable.isRun = false;
|
||||||
/* plcDdj2ReadCustomThread.runnable.isRun = false;
|
/* plcDdj2ReadCustomThread.runnable.isRun = false;
|
||||||
plcDdj3ReadCustomThread.runnable.isRun = false;
|
plcDdj3ReadCustomThread.runnable.isRun = false;
|
||||||
@@ -215,7 +227,95 @@ public class ScheduleThread {
|
|||||||
plcDdj5ReadCustomThread.runnable.isRun = false;
|
plcDdj5ReadCustomThread.runnable.isRun = false;
|
||||||
plcDdj6ReadCustomThread.runnable.isRun = false;*/
|
plcDdj6ReadCustomThread.runnable.isRun = false;*/
|
||||||
plcReadInit();
|
plcReadInit();
|
||||||
isRun = false;
|
}
|
||||||
|
|
||||||
|
private List<Thread> requireFactReaderThreads() {
|
||||||
|
List<Thread> threads = new ArrayList<Thread>();
|
||||||
|
threads.add(requireThread(plcSsjReadCustomThread,
|
||||||
|
"conveyor fact reader"));
|
||||||
|
for (Map.Entry<String, CustomThread<PlcSrgvRead>> entry
|
||||||
|
: plcSrgvReadCustomThreads.entrySet()) {
|
||||||
|
threads.add(requireThread(entry.getValue(),
|
||||||
|
"shuttle fact reader " + entry.getKey()));
|
||||||
|
}
|
||||||
|
threads.add(requireThread(plcTsjReadCustomThread,
|
||||||
|
"lift fact reader"));
|
||||||
|
threads.add(requireThread(plcCdzReadCustomThread,
|
||||||
|
"charger fact reader"));
|
||||||
|
return threads;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Thread> currentFactReaderThreads() {
|
||||||
|
List<Thread> threads = new ArrayList<Thread>();
|
||||||
|
addThreadIfPresent(threads, plcSsjReadCustomThread);
|
||||||
|
for (CustomThread<PlcSrgvRead> reader
|
||||||
|
: plcSrgvReadCustomThreads.values()) {
|
||||||
|
addThreadIfPresent(threads, reader);
|
||||||
|
}
|
||||||
|
addThreadIfPresent(threads, plcTsjReadCustomThread);
|
||||||
|
addThreadIfPresent(threads, plcCdzReadCustomThread);
|
||||||
|
return threads;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void signalFactReadersToStop() {
|
||||||
|
if (plcSsjReadCustomThread != null
|
||||||
|
&& plcSsjReadCustomThread.runnable != null) {
|
||||||
|
plcSsjReadCustomThread.runnable.isRun = false;
|
||||||
|
}
|
||||||
|
for (CustomThread<PlcSrgvRead> reader
|
||||||
|
: plcSrgvReadCustomThreads.values()) {
|
||||||
|
if (reader != null && reader.runnable != null) {
|
||||||
|
reader.runnable.isRun = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (plcTsjReadCustomThread != null
|
||||||
|
&& plcTsjReadCustomThread.runnable != null) {
|
||||||
|
plcTsjReadCustomThread.runnable.isRun = false;
|
||||||
|
}
|
||||||
|
if (plcCdzReadCustomThread != null
|
||||||
|
&& plcCdzReadCustomThread.runnable != null) {
|
||||||
|
plcCdzReadCustomThread.runnable.isRun = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void awaitFactReadersStopped(List<Thread> readerThreads) {
|
||||||
|
long deadline = System.currentTimeMillis() + THREAD_STOP_TIMEOUT_MS;
|
||||||
|
for (Thread readerThread : readerThreads) {
|
||||||
|
long remaining = deadline - System.currentTimeMillis();
|
||||||
|
if (remaining <= 0L) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
readerThread.join(remaining);
|
||||||
|
} catch (InterruptedException interrupted) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"interrupted while stopping PLC fact readers",
|
||||||
|
interrupted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Thread readerThread : readerThreads) {
|
||||||
|
if (readerThread.isAlive()) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"PLC fact reader did not stop: "
|
||||||
|
+ readerThread.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Thread requireThread(CustomThread<?> customThread,
|
||||||
|
String name) {
|
||||||
|
if (customThread == null || customThread.getThread() == null) {
|
||||||
|
throw new IllegalStateException(name + " is not initialized");
|
||||||
|
}
|
||||||
|
return customThread.getThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addThreadIfPresent(List<Thread> threads,
|
||||||
|
CustomThread<?> customThread) {
|
||||||
|
if (customThread != null && customThread.getThread() != null) {
|
||||||
|
threads.add(customThread.getThread());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package com.ruoyi.project.ft546.device;
|
package com.ruoyi.project.ft546.device;
|
||||||
|
|
||||||
import com.ruoyi.common.domain.PlcConnectInfo;
|
import com.ruoyi.common.domain.PlcConnectInfo;
|
||||||
|
import com.ruoyi.common.domain.CustomThread;
|
||||||
import com.ruoyi.device.srgv.domain.DeviceSrgv;
|
import com.ruoyi.device.srgv.domain.DeviceSrgv;
|
||||||
import com.ruoyi.project.ft546.config.Ft546DeviceProfile;
|
import com.ruoyi.project.ft546.config.Ft546DeviceProfile;
|
||||||
import com.ruoyi.project.ft546.device.shuttle.Ft546SrgvRuntimeFactory;
|
import com.ruoyi.project.ft546.device.shuttle.Ft546SrgvRuntimeFactory;
|
||||||
|
import com.ruoyi.schedule.ScheduleThread;
|
||||||
import com.ruoyi.schedule.plcRead.srgv.PlcSrgvRead;
|
import com.ruoyi.schedule.plcRead.srgv.PlcSrgvRead;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@@ -14,6 +16,7 @@ import java.nio.file.Paths;
|
|||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
@@ -55,6 +58,29 @@ class Ft546DeviceRuntimeRegistryTest {
|
|||||||
() -> new Ft546SrgvRuntimeFactory().create(device, profile));
|
() -> new Ft546SrgvRuntimeFactory().create(device, profile));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void factoryRejectsMissingDeviceWithTheDeclaredValidationError() {
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> new Ft546SrgvRuntimeFactory().create(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void startingFactReadersTwiceIsIdempotent() {
|
||||||
|
ScheduleThread schedule = new ScheduleThread();
|
||||||
|
schedule.plcSsjReadCustomThread = noOpThread();
|
||||||
|
schedule.plcTsjReadCustomThread = noOpThread();
|
||||||
|
schedule.plcCdzReadCustomThread = noOpThread();
|
||||||
|
|
||||||
|
schedule.startAllThread();
|
||||||
|
|
||||||
|
assertDoesNotThrow(schedule::startAllThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void stoppingBeforeInitializationIsANoOp() {
|
||||||
|
assertDoesNotThrow(() -> new ScheduleThread().stopAllThread());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void schedulingAndLegacySendPathsDoNotSelectThreeFixedReaders()
|
void schedulingAndLegacySendPathsDoNotSelectThreeFixedReaders()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
@@ -75,6 +101,10 @@ class Ft546DeviceRuntimeRegistryTest {
|
|||||||
new PlcConnectInfo(device.getIp(), device.getPort()));
|
new PlcConnectInfo(device.getIp(), device.getPort()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static <T> CustomThread<T> noOpThread() {
|
||||||
|
return new CustomThread<T>(new Thread(() -> { }), null);
|
||||||
|
}
|
||||||
|
|
||||||
private static DeviceSrgv device(String id) {
|
private static DeviceSrgv device(String id) {
|
||||||
DeviceSrgv device = new DeviceSrgv();
|
DeviceSrgv device = new DeviceSrgv();
|
||||||
device.setDeviceId(Integer.valueOf(id));
|
device.setDeviceId(Integer.valueOf(id));
|
||||||
|
|||||||
Reference in New Issue
Block a user