fix(ft546): serialize shuttle reader lifecycle

This commit is contained in:
zgw
2026-08-11 12:10:24 +08:00
parent 46302c0de1
commit edafe7af94
3 changed files with 151 additions and 18 deletions

View File

@@ -38,6 +38,9 @@ public class Ft546SrgvRuntimeFactory {
}
public PlcSrgvRead create(DeviceSrgv device) {
if (device == null) {
throw new IllegalArgumentException("device identity is required");
}
Ft546DeviceProfile profile = new Ft546DeviceProfile();
profile.setDeviceId(String.valueOf(device.getDeviceId()));
profile.setDeviceType("SHUTTLE");

View File

@@ -30,6 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -66,8 +67,10 @@ public class ScheduleThread {
@Autowired
private Ft546SrgvRuntimeFactory srgvRuntimeFactory;
private boolean isRun = false;
private boolean isRun2 = false;
private volatile boolean isRun = 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() {
plcSsjReadCustomThread.getThread().start();
for (CustomThread<PlcSrgvRead> reader
: plcSrgvReadCustomThreads.values()) {
reader.getThread().start();
public synchronized void startAllThread() {
if (isRun) {
return;
}
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;
}
@@ -200,14 +208,18 @@ public class ScheduleThread {
/**
* 停止线程
*/
public void stopAllThread() {
plcSsjReadCustomThread.runnable.isRun = false;
for (CustomThread<PlcSrgvRead> reader
: plcSrgvReadCustomThreads.values()) {
reader.runnable.isRun = false;
public synchronized void stopAllThread() {
if (!isRun) {
return;
}
List<Thread> readerThreads = currentFactReaderThreads();
signalFactReadersToStop();
isRun = false;
awaitFactReadersStopped(readerThreads);
plcSrgvReadCustomThreads.clear();
deviceRuntimeRegistry.clear();
if (deviceRuntimeRegistry != null) {
deviceRuntimeRegistry.clear();
}
// plcDdj1ReadCustomThread.runnable.isRun = false;
/* plcDdj2ReadCustomThread.runnable.isRun = false;
plcDdj3ReadCustomThread.runnable.isRun = false;
@@ -215,7 +227,95 @@ public class ScheduleThread {
plcDdj5ReadCustomThread.runnable.isRun = false;
plcDdj6ReadCustomThread.runnable.isRun = false;*/
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());
}
}
/**

View File

@@ -1,9 +1,11 @@
package com.ruoyi.project.ft546.device;
import com.ruoyi.common.domain.PlcConnectInfo;
import com.ruoyi.common.domain.CustomThread;
import com.ruoyi.device.srgv.domain.DeviceSrgv;
import com.ruoyi.project.ft546.config.Ft546DeviceProfile;
import com.ruoyi.project.ft546.device.shuttle.Ft546SrgvRuntimeFactory;
import com.ruoyi.schedule.ScheduleThread;
import com.ruoyi.schedule.plcRead.srgv.PlcSrgvRead;
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.assertEquals;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -55,6 +58,29 @@ class Ft546DeviceRuntimeRegistryTest {
() -> 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
void schedulingAndLegacySendPathsDoNotSelectThreeFixedReaders()
throws Exception {
@@ -75,6 +101,10 @@ class Ft546DeviceRuntimeRegistryTest {
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) {
DeviceSrgv device = new DeviceSrgv();
device.setDeviceId(Integer.valueOf(id));