Skip to content

Commit

Permalink
#3088 Fixed StackOverflowError for Cyclic Dependency in Meta Data Sou…
Browse files Browse the repository at this point in the history
…rce:

- Added validate cyclic dependency in methods: MetaPointLocatorRT.pointInitialized, MetaPointLocatorRT.pointTerminated;
- User Login Event set state 'Return To Normal' after logout user;
- Refactoring for recursive actions: SearchCyclicDependencyAction, SetUnreliableDataPointsAction, CollectMetaDataPointFromContextAction, SearchOpcUaNodesAction;
- Added param 'scadalts.validation.search-cyclic-depth' to env.properties;
- Invoke mangoContextListener.contextInitialized is not initialized mangoContextListener;
- Invoke 'Return To Normal' again only if changed unreliable;
- Added test cases in: CyclicDependencyValidationUtilsTest, DataPointUnreliableUtilsTest;
- Optimized test, closed timer, reduce number threads 'Serotonin Timer': DataPointUnreliableUtilsTest;
  • Loading branch information
Limraj committed Feb 14, 2025
1 parent 9fa1f6d commit 97217ce
Show file tree
Hide file tree
Showing 35 changed files with 683 additions and 331 deletions.
8 changes: 8 additions & 0 deletions src/com/serotonin/mango/MangoContextListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@
public class MangoContextListener implements ServletContextListener {
private final Log log = LogFactory.getLog(MangoContextListener.class);

private boolean initialized;

@Override
public void contextInitialized(ServletContextEvent evt) {
try {
Expand All @@ -103,12 +105,18 @@ public void contextInitialized(ServletContextEvent evt) {
SystemEventType.TYPE_SYSTEM_STARTUP), System
.currentTimeMillis(), false, new LocalizableMessage(
"event.system.startup"));
initialized = true;
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
initialized = false;
throw ex;
}
}

public boolean isInitialized() {
return initialized;
}

private void initialized(ServletContextEvent evt) {
log.info("Scada-LTS context starting at: " + Common.getStartupTime());

Expand Down
11 changes: 10 additions & 1 deletion src/com/serotonin/mango/rt/RuntimeManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -1092,9 +1092,18 @@ private void stopPoints() {
}

public List<DataPointRT> getRunningMetaDataPoints(int dataPointInContextId) {
return getRunningMetaDataPoints(dataPointInContextId, dataPoint -> true);
}

public List<DataPointRT> getRunningMetaDataPoints(int dataPointInContextId, boolean unreliable) {
return getRunningMetaDataPoints(dataPointInContextId, dataPoint -> dataPoint.isUnreliable() == unreliable);
}

public List<DataPointRT> getRunningMetaDataPoints(int dataPointInContextId, Predicate<DataPointRT> condition) {
Map<Integer, DataPointRT> dataPoints = new HashMap<>(this.dataPoints);
return filterRunningDataPoints(dataPoints.values(), dataPoint -> isMetaDataPointRT(dataPoint)
&& isDataPointInContext(dataPoint, dataPointInContextId));
&& isDataPointInContext(dataPoint, dataPointInContextId)
&& condition.test(dataPoint));
}

private static List<DataPointRT> filterRunningDataPoints(List<DataPointRT> dataPoints, Predicate<DataPointRT> filter) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,21 @@
package com.serotonin.mango.rt.dataSource;

import com.serotonin.mango.Common;
import com.serotonin.mango.rt.dataImage.DataPointRT;
import com.serotonin.mango.util.LoggingUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.scada_lts.recursive.SetUnreliableDataPointsAction;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public final class DataPointUnreliableUtils {

private static final Log LOG = LogFactory.getLog(DataPointUnreliableUtils.class);

private static final String ATTR_UNRELIABLE_KEY = "UNRELIABLE";
private static final int SAFE = 10;

private DataPointUnreliableUtils() {}

public static boolean isSetUnreliable(DataPointRT dataPoint, boolean unreliable) {
return dataPoint.getAttribute(ATTR_UNRELIABLE_KEY) instanceof Boolean
&& ((boolean) dataPoint.getAttribute(ATTR_UNRELIABLE_KEY)) == unreliable;
}

public static void setUnreliableDataPoints(List<DataPointRT> dataPoints) {
unreliable(dataPoints, true, SAFE);
Expand All @@ -40,31 +33,16 @@ public static void resetUnreliableDataPoint(DataPointRT dataPoint) {
unreliable(Collections.singletonList(dataPoint), false, SAFE);
}

private static void unreliable(List<DataPointRT> dataPoints, boolean unreliable, int safe) {
setAttributes(filter(dataPoints, unreliable), unreliable);
for(DataPointRT dataPoint: dataPoints) {
List<DataPointRT> metaDataPoints = Common.ctx.getRuntimeManager().getRunningMetaDataPoints(dataPoint.getId());
if(!metaDataPoints.isEmpty()) {
if(safe > -1)
unreliable(metaDataPoints, unreliable, --safe);
else {
LOG.warn("The safe counter has been exceeded!: " + LoggingUtils.dataPointInfo(dataPoint));
setAttributes(filter(metaDataPoints, unreliable), unreliable);
}
}
private static void unreliable(List<DataPointRT> dataPoints, boolean unreliable, int depth) {
SetUnreliableDataPointsAction setUnreliableDataPointsAction = new SetUnreliableDataPointsAction(dataPoints, unreliable, depth);
try {
setUnreliableDataPointsAction.call();
} catch (Exception e) {
LOG.error(LoggingUtils.exceptionInfo(e));
}
}

private static void setAttributes(List<DataPointRT> dataPoints, boolean unreliable) {
for (DataPointRT dataPoint : dataPoints) {
dataPoint.setAttribute(ATTR_UNRELIABLE_KEY, unreliable);
}
public static boolean isSetUnreliable(DataPointRT dataPointRT, boolean unreliable) {
return SetUnreliableDataPointsAction.isSetUnreliable(dataPointRT, unreliable);
}

private static List<DataPointRT> filter(List<DataPointRT> dataPoints, boolean unreliable) {
return dataPoints.stream().filter(dataPoint -> !isSetUnreliable(dataPoint, unreliable))
.collect(Collectors.toList());
}


}
56 changes: 31 additions & 25 deletions src/com/serotonin/mango/rt/dataSource/meta/MetaPointLocatorRT.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@

import java.text.MessageFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.*;

import javax.script.ScriptException;

Expand All @@ -38,7 +34,7 @@
import com.serotonin.mango.rt.dataImage.PointValueTime;
import com.serotonin.mango.rt.dataSource.PointLocatorRT;
import com.serotonin.mango.util.DateUtils;
import com.serotonin.mango.util.LoggingUtils;
import com.serotonin.mango.vo.DataPointVO;
import com.serotonin.mango.vo.dataSource.meta.MetaPointLocatorVO;
import com.serotonin.timer.AbstractTimer;
import com.serotonin.timer.CronExpression;
Expand All @@ -48,6 +44,7 @@
import com.serotonin.web.i18n.LocalizableMessage;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.scada_lts.utils.ValidationUtils;

import static com.serotonin.mango.util.LoggingScriptUtils.generateContext;
import static com.serotonin.mango.util.LoggingScriptUtils.infoErrorExecutionScript;
Expand All @@ -70,9 +67,6 @@ public class MetaPointLocatorRT extends PointLocatorRT implements DataPointListe
boolean initialized;
TimerTask timerTask;

private final AtomicInteger pointInitializedSafe = new AtomicInteger(MAX_RECURSION);
private final AtomicInteger pointTerminatedSafe = new AtomicInteger(MAX_RECURSION);

private static final Log LOG = LogFactory.getLog(MetaPointLocatorRT.class);

public MetaPointLocatorRT(MetaPointLocatorVO vo) {
Expand Down Expand Up @@ -179,16 +173,14 @@ public void pointInitialized() {
return;
}

if(pointInitializedSafe.getAndDecrement() < 0) {
LOG.error("Exceeded recursive level: " + LoggingUtils.dataPointInfo(dataPoint));
pointInitializedSafe.set(MAX_RECURSION);
return;
}

if(dataPoint.getPointLocator() instanceof MetaPointLocatorRT) {
DataPointListener dataPointListener = Common.ctx.getRuntimeManager().getDataPointListeners(dataPoint.getId());
if(dataPointListener != null) {
dataPointListener.pointInitialized();
if(dataPointListener != null && dataPointListener != this) {
if(dataPointListener instanceof MetaPointLocatorRT) {
MetaPointLocatorRT fromContext = (MetaPointLocatorRT)dataPointListener;
execute(dataPoint, fromContext, dataSource, fromContext::pointInitialized);
}

}
}
}
Expand All @@ -197,20 +189,21 @@ public void pointTerminated() {

context = createContext(dataPoint);

if(pointTerminatedSafe.getAndDecrement() < 0) {
LOG.error("Exceeded recursive level: " + LoggingUtils.dataPointInfo(dataPoint));
pointTerminatedSafe.set(MAX_RECURSION);
return;
}

if(dataPoint.getPointLocator() instanceof MetaPointLocatorRT) {
DataPointListener dataPointListener = Common.ctx.getRuntimeManager().getDataPointListeners(dataPoint.getId());
if(dataPointListener != null) {
dataPointListener.pointTerminated();
if(dataPointListener != null && dataPointListener != this) {
if(dataPointListener instanceof MetaPointLocatorRT) {
MetaPointLocatorRT fromContext = (MetaPointLocatorRT)dataPointListener;
execute(dataPoint, fromContext, dataSource, fromContext::pointTerminated);
}
}
}
}

public DataPointRT getDataPoint() {
return dataPoint;
}

//
//
// TimeoutClient
Expand Down Expand Up @@ -407,4 +400,17 @@ private static boolean isUpdatePoint(boolean initializeMode, PointValueTime valu
&& metaPointLocator.getUpdateEvent() != MetaPointLocatorVO.UPDATE_EVENT_CONTEXT_UPDATE)
|| (previousValueTime == null || !ObjectUtils.isEqual(valueTime.getValue(), previousValueTime.getValue()));
}

private static void execute(DataPointRT dataPointStart, MetaPointLocatorRT fromContext, MetaDataSourceRT dataSource,
Runnable execute) {
DataPointRT dataPointRtFromContext = fromContext.getDataPoint();
DataPointVO dataPointVoFromContext = dataPointRtFromContext.getVO();

if(ValidationUtils.isCyclicDependency(dataPointStart.getId(), dataPointRtFromContext.getId())) {
dataSource.raiseRecursiveError(System.currentTimeMillis(), dataPointStart,
new LocalizableMessage("validate.cyclicDependency", dataPointVoFromContext.getName()));
} else {
execute.run();
}
}
}
52 changes: 14 additions & 38 deletions src/com/serotonin/mango/util/StartStopDataPointsUtils.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package com.serotonin.mango.util;

import com.serotonin.db.IntValuePair;
import com.serotonin.mango.rt.dataImage.DataPointRT;
import com.serotonin.mango.rt.dataSource.DataSourceRT;
import com.serotonin.mango.vo.DataPointVO;
import com.serotonin.mango.vo.dataSource.PointLocatorVO;
import com.serotonin.mango.vo.dataSource.meta.MetaPointLocatorVO;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.scada_lts.mango.service.DataPointService;
import org.scada_lts.recursive.CollectMetaDataPointFromContextAction;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.*;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -63,11 +64,11 @@ private static void execute(List<DataPointVO> firstExecute, List<DataPointVO> se
}

private static List<DataPointVO> getSequenceMetaDataPoints(Predicate<Integer> isExecute, List<DataPointVO> metaDataPoints) {
List<DataPointVO> sequenceDataPoints = new ArrayList<>();
Set<Integer> toCheck = new HashSet<>();
int safe = 10;
List<DataPointVO> sequenceDataPoints = new CopyOnWriteArrayList<>();
Set<Integer> toCheck = new CopyOnWriteArraySet<>();
int depth = 100;
for(DataPointVO dataPoint: metaDataPoints) {
collectMetaDataPointsFromContext(toCheck, sequenceDataPoints, dataPoint, safe, metaDataPoints, isExecute);
collectMetaDataPointsFromContext(toCheck, sequenceDataPoints, dataPoint, depth, metaDataPoints, isExecute);
}
return sequenceDataPoints;
}
Expand Down Expand Up @@ -95,40 +96,15 @@ private static <T> void execute(List<T> objects, Predicate<T> isExecute, Consume
}

private static void collectMetaDataPointsFromContext(Set<Integer> toCheck, List<DataPointVO> toRunning,
DataPointVO dataPoint, int safe, List<DataPointVO> dataPoints,
DataPointVO dataPoint, int depth, List<DataPointVO> dataPoints,
Predicate<Integer> isExecute) {
if(safe < 0) {
LOG.error("Recursion level exceeded: " + LoggingUtils.dataPointInfo(dataPoint));
return;
}
if(dataPoint.isEnabled()) {
PointLocatorVO pointLocator = dataPoint.getPointLocator();
if(pointLocator instanceof MetaPointLocatorVO) {
updateList(toCheck, toRunning, dataPoint);
MetaPointLocatorVO metaPointLocator = (MetaPointLocatorVO) pointLocator;
List<IntValuePair> context = metaPointLocator.getContext();
if(context != null && !context.isEmpty()) {
for(IntValuePair intValuePair : context) {
if(intValuePair.getKey() > 0 && isExecute.test(intValuePair.getKey())) {
DataPointVO fromContextDataPoint = dataPoints.stream()
.filter(point -> point.getId() == intValuePair.getKey())
.findAny()
.orElse(null);
if (fromContextDataPoint != null && (fromContextDataPoint.getPointLocator() instanceof MetaPointLocatorVO)) {
collectMetaDataPointsFromContext(toCheck, toRunning, fromContextDataPoint, --safe, dataPoints, isExecute);
}
}
}
}
}
}
}

private static void updateList(Set<Integer> toCheck, List<DataPointVO> toRunning, DataPointVO dataPoint) {
if (toCheck.contains(dataPoint.getId())) {
toRunning.removeIf(toRunningPoint -> toRunningPoint.getId() == dataPoint.getId());
CollectMetaDataPointFromContextAction metaDataPointCollector =
new CollectMetaDataPointFromContextAction(toCheck, toRunning, dataPoint, depth, dataPoints, isExecute);
try {
metaDataPointCollector.call();
} catch (Exception e) {
LOG.error(LoggingUtils.exceptionInfo(e));
}
toCheck.add(dataPoint.getId());
toRunning.add(dataPoint);
}
}
8 changes: 6 additions & 2 deletions src/com/serotonin/mango/util/ThreadPoolExecutorUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,15 @@ public static ForkJoinPool createForkJoinPool() {
}

public static void joinTermination(ExecutorService service, String poolName) {
joinTermination(service, poolName, 5, TimeUnit.SECONDS);
}

public static void joinTermination(ExecutorService service, String poolName, long timeoutSeconds, TimeUnit timeUnit) {
boolean done;
try {
int rewaits = 3;
while (rewaits > 0) {
done = service.awaitTermination(5, TimeUnit.SECONDS) && service.isTerminated();
done = service.awaitTermination(timeoutSeconds, timeUnit) && service.isTerminated();

if (done)
break;
Expand All @@ -77,7 +81,7 @@ public static void joinTermination(ExecutorService service, String poolName) {

rewaits--;
}
if(!service.isTerminated() && !service.awaitTermination(5, TimeUnit.SECONDS)) {
if(!service.isTerminated() && !service.awaitTermination(timeoutSeconds, timeUnit)) {
service.shutdownNow();
}
} catch (InterruptedException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.serotonin.db.IntValuePair;
import com.serotonin.json.JsonArray;
Expand All @@ -46,13 +44,11 @@
import com.serotonin.mango.vo.DataPointVO;
import com.serotonin.mango.vo.dataSource.AbstractPointLocatorVO;
import com.serotonin.mango.vo.TimePeriodType;
import com.serotonin.mango.vo.dataSource.PointLocatorVO;
import com.serotonin.timer.CronTimerTrigger;
import com.serotonin.util.SerializationHelper;
import com.serotonin.util.StringUtils;
import com.serotonin.web.dwr.DwrResponseI18n;
import com.serotonin.web.i18n.LocalizableMessage;
import org.scada_lts.mango.service.DataPointService;

import static org.scada_lts.utils.ValidationDwrUtils.validateVarNameScript;
import static org.scada_lts.utils.ValidationUtils.isCyclicDependency;
Expand Down Expand Up @@ -189,17 +185,14 @@ public void validate(DwrResponseI18n response, int dataPointId) {
if (StringUtils.isEmpty(script))
response.addContextualMessage("script", "validate.required");

DataPointService dataPointService = new DataPointService();
Map<Integer, DataPointVO> dataPoints = dataPointService.getDataPoints(null, true)
.stream()
.collect(Collectors.toMap(DataPointVO::getId, Function.identity()));


List<String> varNameSpace = new ArrayList<>();
for (IntValuePair point : context) {
String varName = point.getValue();
int pointId = point.getKey();

if(pointId != Common.NEW_ID && isCyclicDependency(pointId, dataPointId, dataPoints, 10)) {
if(pointId != Common.NEW_ID && isCyclicDependency(pointId, dataPointId)) {
response.addContextualMessage("context", "validate.cyclicDependency", escapeHtml(varName));
break;
}
Expand Down
Loading

0 comments on commit 97217ce

Please sign in to comment.