Hi,
I am writing a history provider. This history provider can have multiple instances each with a Tag History Provider and a Data Sink. During the startup of my module, I am looping through each Tag Provider and caching all tag properties. The tag to grab all tags using a history provider is slow, so I am doing all this work on a future. Though inefficient, I have to loop through all tags as seen in this example. Everything in my browse function is async and I will eventually have to send all the tags for each instance to my historian through its web api.
The problem I am having is that whenever I restart my module in the gateway, it takes a long time as Ignition takes almost all of my CPU. I did some research and saw that Zulu does not handle futures well and tried updating my code to use thread pools, but this still doesn’t help things.
During startup I essentially need to do the following:
- Find all tags
- Pull each tag’s configuration (since browseNodeAsync does not grab this)
- Cache the tag if the tag’s history provider matches one of my instances
- Send all the cached tags to my API per instances
Any help would be appreciated!
private CompletableFuture<List<TagPath>> browseNodeAsync(TagProvider provider, TagPath parentPath) {
return provider.browseAsync(parentPath, BrowseFilter.NONE)
.thenComposeAsync(results -> {
if (results.getResultQuality().isNotGood()) {
return CompletableFuture.failedFuture(new Exception("Bad quality results: " + results.getResultQuality()));
}
Collection<NodeDescription> nodes = results.getResults();
List<CompletableFuture<List<TagPath>>> childFutures = new ArrayList<>();
List<TagPath> paths = new ArrayList<>();
if (nodes != null){
for (NodeDescription node : nodes) {
TagPath fullPath = node.getFullPath();
paths.add(fullPath);
if (node.hasChildren() && node.getDataType() != DataType.Document) {
TagPath childPath = parentPath.getChildPath(node.getName());
childFutures.add(browseNodeAsync(provider, childPath));
}
}
}
return CompletableFuture.allOf(childFutures.toArray(new CompletableFuture[0]))
.thenApply(v -> childFutures.stream()
.map(CompletableFuture::join)
.flatMap(List::stream)
.collect(Collectors.toCollection(() -> paths)));
});
}
private CompletableFuture<List<QualityCode>> initializeCacheForProviderAsync(TagProvider provider, Set<String> names) {
TagPath root;
try {
root = TagPathParser.parse("");
} catch (IOException e) {
return CompletableFuture.failedFuture(e);
}
if (root != null){
return browseNodeAsync(provider, root)
.thenCompose(paths -> provider.getTagConfigsAsync(paths, true, false))
.thenCompose(tagConfigs -> {
List<TagConfiguration> tagConfigsToUpdate = new ArrayList<>();
for (TagConfiguration tagConfig : tagConfigs) {
var historyProvider = tagConfig.getOrDefault(WellKnownTagProps.HistoryProvider);
if (tagConfig.getType() != TagObjectType.Folder && historyProvider != null && names.contains(historyProvider)) {
var tagId = tagConfig.get(MyModule.MyProperty);
if (tagId == null) {
tagConfig.set(PropertyValue.of(MyModule.MyProperty, UUID.randomUUID().toString()));
tagConfigsToUpdate.add(tagConfig);
}
var propertiesByTagPath = PropertiesByInstanceThenByTagPath.get(historyProvider);
propertiesByTagPath.put(tagConfig.getPath(), tagConfig);
}
}
if (!tagConfigsToUpdate.isEmpty()) {
return provider.saveTagConfigsAsync(tagConfigsToUpdate, CollisionPolicy.MergeOverwrite);
} else {
return CompletableFuture.completedFuture(null);
}
});
}
else{
return CompletableFuture.completedFuture(null);
}
}
public void startup(LicenseState activationState) {
List<CompletableFuture<Void>> instanceInitFutures = new ArrayList<>();
startedFuture = CompletableFuture
.allOf(context.getTagManager().getTagProviders().stream()
.map(provider -> initializeCacheForProviderAsync(provider, names))
.toArray(CompletableFuture[]::new))
.thenComposeAsync(v -> {
// Initialize instances in parallel
for (MyPersistentRecord record : records) {
CompletableFuture<Void> instanceFuture = instanceManager.addAsync(record);
instanceInitFutures.add(instanceFuture);
}
return CompletableFuture.allOf(instanceInitFutures.toArray(new CompletableFuture[0]));
}, ASYNC_EXECUTOR)
.whenComplete((result, ex) -> {
if (ex == null) {
log.info("My module started successfully.");
} else {
log.error("My failed to start successfully.", ex);
}
});
}