jkisoftware / jki-state-machine-objects Goto Github PK
View Code? Open in Web Editor NEWObject-oriented framework for LabVIEW based on the JKI State Machine
License: BSD 3-Clause "New" or "Revised" License
Object-oriented framework for LabVIEW based on the JKI State Machine
License: BSD 3-Clause "New" or "Revised" License
Hello,
i have created a SMO for an ethernet IO-node. This SMO was used as shared resource in multiple SMOs representing pneumatic subsystems in my environment. I used enumerateStaticDependencies to define dependancies and this worked very well. I could start the different pneumatic SMOs in parallel using their test launcher. Finally i started composing my machine controller using the pneumatic SMOs. This also worked well.
Since release 1.1.15 i only can start 1 pneumatic SMO successfully. Even the machine controller composition can't launch the dependencies successfully.
The release 1.1.15 changed the output of smo.lvclass:ListDependencies.vi. The output now is filtered by a component shared key. The shared key is calculated from a SecretKey and GUID and changes every time an input changes. So I never get my component shared to other SMOs.
How do i have to implement my shared dependencies now?
One name limitation that exists right now is that we can have a situation where we have two pumps in a system, and we could not both call them pumps even though they have different qualified paths.
Since we have a GUID for each object (already a unique identifier), it would be a good feature to be able to have qualified path unicity instead of name unicity.
This feature implies a displayed name that does not have to be unique, and a unique qualified path (constructed from the composition/aggregation tree).
Currently, there is no mean to declare a subsystem as shared dependency without having an owner which manages the lifetime of the shared instance. Feature request is to allow a subsystem to self-declare itself as a shared dependency, by defining itself as owner. (Filtering should be done at start/stop methods to prevent recursive calls or doubling of instances.)
An asynchronous wrapper call on the dynamically dispatched callbacks would ensure that no race conditions or mishandled errors can block the workflow.
Callback VIs (onStart, onStop, onCreated, etc.) can make recursive calls to dependencies, which might contain blocking conditions that cannot be terminated gracefully. If the callback is wrapped in an async method waiting for execution to complete, framework can enforce thread termination on error. (This mechanism should work for both byVal and byRef classes)
The common way to communicate with SMO is by exchanging events, but sometimes many SMOs would register and send the same message, so there would be many ways to get around this, even by adding a message indentifier or a wait reply using DVR + occurences.
Occurences would be vulnerable to race conditions...
Example:
Consider "SMO.Mongo.lvclass", a SMO that insert & loads variants from MongoDB.
I want this class to be extensible, so normally projects with complex queries would inherit it.
All SMO that DB access would composite SMO.Mongo.lvclass, like UI.UserManager.lvclass would call API "Put Document.vi" normally, but "Load Document by ID.vi" would need a reply. I could easily check if an incoming event with document _id reply at "UI.UserManager", but at some cases when a list of documents is asked there is no parameter for verification, so I would need to add a field in "List Documents.vi" api call like "call-key" and send this in replies, so I could get an addressed event, all event structures waiting that case would need to check if the key matches, but I don't like this solution as it seems a lot of overload.
Another idea I am thinking is to make this a byRef SMO, and after API call wait on occurence, so when SMO.Mongo loads response in a proper DVR it fires the occurence, so API call can read DVR with response.
Of course that occurences are not safe, but then a 'call key' could be added, and if this is the case, why not create a queue, message the SMO with that queue and wait for element then destory? Or why not using then Notifier?
I don't know what is the best option and why I should use one over the other option.
This SMO.Mongo.lvclass is a real project I'm planning to write in order to stop worring with database structure.
Currently I create 2 projects with a SMO DBs (EnduranceDB.lvclass & DynaDB.lvclass) using Mongo.lvclass (source avaliable in my repo) and make all project logic there, but then for each project I will need to change my reusable subsystems, like UI.UserManager.lvclass. So I want to create a SMO.Mongo.lvclass and make layers of specialization, so I could make Mongo.Endurance.lvclass, Endurance project have user authentication, so I just add UI.UserManager.lvclass and it already works with my Mongo.Endurance.lvclass as it is a SMO.Mongo.lvclass.
Currently I need different source for UI.UserManager in 2 projects, as the SMO DB is different.
When multiple components are shared resources in a system, it might be critical to limit certain functionalities to a master caller during certain states.
An example would be where a pump is being controlled (Start-Stop) by a system and interlocks are to be put in place to disallow a remote call from stopping the pump while the owner system is in a critical state where pump should never be turned off. The system would reserve the component and be the sole caller which can change the state of this component. It could then release the component when it itself transitions to a maintenance mode, allowing for engineering/manual/test UIs to take control of the pump without causing a conflict during critical state.
When trying to open editor it says that the VI is saved in 2014. Seems like someone hit a wrong save button.
Starting a SMO in a process.vi between the "Register Process" node and the Process: Sync case deadlocks. The issue is that the Register Process node is not reentrant.
Verify that making this node "pre-allocated clones" does not hinder any of the other framework features.
When I use the SMO Editor to add a Public Event, a Public Method, or a Private Event, it results in:
Unknown. I can drag the new event ref num into the typedef's cluster and resolve the error, but I'm unsure if the SMO Editor stopped its operation early and left other VIs unmodified. I'm worried the SMO is now incoherent or incomplete.
Error 1054 occurred at New VI Object in ProjectItem.JKI SMO.lvclass:Add PublicEventToDataCluster.vi->ProjectItem.JKI SMO.lvclass:Add New Event.vi:690001->ProjectItem.JKI SMO.lvclass:Add ProtectedEvents--DataCluster.vi->JKI SMO Editor.lvclass:AddEvent.vi
Possible reason(s):
LabVIEW: (Hex 0x41E) The specified object was not found.
Error 1054 occurred at New VI Object in ProjectItem.JKI SMO.lvclass:Add PublicEventToDataCluster.vi->ProjectItem.JKI SMO.lvclass:Add New Event.vi:690001->ProjectItem.JKI SMO.lvclass:Add ProtectedEvents--DataCluster.vi->JKI SMO Editor.lvclass:AddEvent.vi
Possible reason(s):
LabVIEW: (Hex 0x41E) The specified object was not found.
Error 1054 occurred at New VI Object in ProjectItem.JKI SMO.lvclass:Add PublicEventToDataCluster.vi->ProjectItem.JKI SMO.lvclass:Add New Event.vi:690001->ProjectItem.JKI SMO.lvclass:Add PrivateEvents--DataCluster.vi->JKI SMO Editor.lvclass:AddEvent.vi
Possible reason(s):
LabVIEW: (Hex 0x41E) The specified object was not found.
JKI SMO Editor v2.0.1.4 by JKI
Released On: Wed, 27 Apr 2016 08:59:07 -0700
Author: JKI
Copyright: Copyright (c) 2016, JKI
Compatible LabVIEW Versions: >= 2013.
Compatible OS Versions: ALL.
Repository Name: JKI Package Network
Release Notes:
- Changed Public Method to use Private Events to message Process.
- Event can now be empty (boolean placeholder)
- Event that contains only one control will not wrap into a cluster
This Package depends on these other packages:
jki_lib_state_machine >= 2.0.0
OpenG Array Library >= 4.1.1.14
OpenG Error Library >= 4.2.0.23
OpenG File Library >= 4.0.1.22
OpenG LabVIEW Data Library >= 4.2.0.21
OpenG String Library >= 4.1.0.12
OpenG Variant Configuration File Library >= 4.0.0.5
JKI SMO Templates >= 1.0.0.5
JKI State Machine Objects (SMO) >= 1.0.0.11
Currently, the composition tree is flattened using the dot notation.
This is a thread to discuss whether this is an optimal pattern for SMOs.
For example, an instrument of "Instrument.ModelA" class that depends on a "Tool.ModelB" object would create two objects with default names (Qualified Names):
Unique names for SMOs currently remove invalid characters, including dots, to allow dot notation to be used in the flattening of the objects composition lineage. Other option could be to use "/" for separators in the flattened lineage.
A general question about setting up the preferred execution system of a process in the JKI-SMO architecture. I appreciate any help and insight that can be provided.
I have a JKI-SMO with a process which is getting blocked when a screen in another JKI-SMO opens the windows file dialog box. I believe I understand why it is getting blocked (both the screen and process are ending up in the user interface [UI] preferred execution system [PES], oh, or the process is doing something [like creating a queue] which can only runs in the UI PES).
Is getting the process to run in another PES, as simple as changing the PES of the process VI in the VI Properties dialog box? Because I have tried that and it doesn't seem to help. But I am going to review the process and see what resources it creates, in case that is the cause of the issue.
Are there any tools to help debug the PES (ie, what VI is in what PES)? I tried the Desktop Execution Trace Toolkit, but didn't have any luck. It does give thread ID but mapping that to PES......???
onProcessStart( ) is currently called before the process thread is launched async.
It should be executed after to mirror the onProcessStopped( ) callback being called in the process thread.
When an SMO declares a static dependency, it creates and starts it as part of its own starting process. Currently, an action in the dependency process can occur before its owner has registered to message event generated by this dependency, resulting in a message lost.
To Reproduce
Steps to reproduce the behavior:
Possible Solution
Create the registration mailbox before launching dependencies to queue up the message (events) and act on them once the caller's process is started.
The JKI SMO Editor removes the Typedefs from my SMO when I add a public method.
Usually, when a tool's operations on user data can fail, they can be rolled back by the tool. It would be helpful if these workflows were made transactional: apply-or-noop.
Error 8 occurred at Invoke Node in ProjectItem.JKI SMO.lvclass:New PublicEvents--DataCluster.vi->ProjectItem.JKI SMO.lvclass:Add New Event.vi:690001->ProjectItem.JKI SMO.lvclass:Add ProtectedEvents--DataCluster.vi->JKI SMO Editor.lvclass:AddEvent.vi
Possible reason(s):
LabVIEW: (Hex 0x8) File permission error. You do not have the correct permissions for the file.
Method Name: Save:Instrument
JKI SMO Editor v2.0.1.4 by JKI
Released On: Wed, 27 Apr 2016 08:59:07 -0700
Author: JKI
Copyright: Copyright (c) 2016, JKI
Compatible LabVIEW Versions: >= 2013.
Compatible OS Versions: ALL.
Repository Name: JKI Package Network
Release Notes:
- Changed Public Method to use Private Events to message Process.
- Event can now be empty (boolean placeholder)
- Event that contains only one control will not wrap into a cluster
This Package depends on these other packages:
jki_lib_state_machine >= 2.0.0
OpenG Array Library >= 4.1.1.14
OpenG Error Library >= 4.2.0.23
OpenG File Library >= 4.0.1.22
OpenG LabVIEW Data Library >= 4.2.0.21
OpenG String Library >= 4.1.0.12
OpenG Variant Configuration File Library >= 4.0.0.5
JKI SMO Templates >= 1.0.0.5
JKI State Machine Objects (SMO) >= 1.0.0.11
A child process that stops due to an error would only trigger a onProcessStopped event after the calling thread has requested a the subsystem to stop. There is currently no notifications that the process has stopped until then. It is called when requested by the caller thread, but not on error or self-termination.
I hesitate to call that a bug, but it is certainly not an optimal arrangement.
At the same time, to achieve a symmetrical SMO lifecycle, the onProcessStarted() and onProcessStopped() should be in the process thread, whereas onStarted() should be moved to the calling thread.
I have a system which works as expected when testing the subsystems, but when it's called by the full program the execution sometimes get delayed.
Aquisiton loop runs at 100 ms and send messages to control and UI. Control implements a simple P.I.. Without UI the Control loop runs correctly at max 102ms, but when UI is running together it runs from 100ms to 300ms, and this makes the control response too slow.
I believe LabVIEW is priorizing the UI updates, that are less important then the control.
How can I configure execution & messaging priorities in SMO?
bug
It has been reported that a memory leak occurs when destroying an SMO that is not owned by another SMO through composition.
The state event is not destroyed by the object to allow callers to get a notification on self-termination of one of its children SMOs. This method leaks the reference if the SMO is standalone or if the SMO leaves memory before the caller has created a mailbox for the state event.
Fix will need to include that the SMO has an attribute to give it knowledge that it is "owned" by another process so that it can handle its own reference on shutdown when appropriate. Destruction when owned will need to include a callback from owner to acknowledge destruction of child process.
I would recommend JKI to use gitter. I don't know how big is the LabVIEW users that use github/gitter, but thats a really useful tool and I would use, instead of posting multiple comments on issues.
So this way: 1. Post a issue with known details; then discuss in gitter
This needs to be reproduced as it is supposed to be supported.
Two subsystems that use a third subsystem as a dependency (ex: logger) can sometimes create two instances of the logger class instead of sharing a single instance.
Instances are supposed to be shared if the instance name is identical and if the declared type matches.
Hint to reproduce/identify this bug, look for different inheritance levels that do not resolve correctly or for a race condition in the registration of a new subsystem in the Registry class.
ListDependencies is a CPU intensive method at the moment due to process of comparing GUIDs and calculating MD5s at every call. It would be useful to improve the performance of the calls with a caching scheme that is refreshed on dependency changes at runtime.
Add these new Unit Tests and solve.
Instrument?encoding=text works.
/Instrument?encoding=text fails.
/Instrument/Pump?encoding=text fails.
Instrument/Pump?encoding=text fails.
Owner should be able to stop dynamically-launched dependency(ies) without the process being automated in the onStop() callback.
For now, the dependencies cannot be stopped without a shared key, but the shared key can only be generated by base class, which prevents managing dynamically-launched objects without stopping caller's process (called during onStop override only).
Solution: expose a method to stop a dynamic dependency by name or ref.
Using the Launch Dependency public method does not return the SMO reference. Exposing this reference to the caller of the launched dependency would allow registering to its public events.
I can start dependencies of actors in various ways.
Nested dependency management:
Where to do this:
Or simply:
What is working best for me is the "main SMO" managing all dependencies and by overwriting the corresponding action vi.
When a dependency is owned by a caller object, there is currently no way for the dependency to self-terminate and notify its caller. The caller is the only one able to terminate with use of "shared key". There should be a way for the dependencies to terminate independently through the use of its "private key". This action should notify the caller which should automatically remove the dependency from its list.
This addresses the use case of a caller dispatching work to another SMO and expecting it to self-terminate on completion of the task.
Current workaround is to implement an event that the callee can generate for its caller to monitor, thus creating a tight coupling.
Rework attributes to be more efficient, especially to search operations.
Using native Variant Attributes with attributes classes would speed this up.
Currently, an SMO will turn into a "zombie" and wait forever if the call chain of the creator goes idle -- this happens because LabVIEW garbage collects all DVRs, Event References, Refnums, etc. created in the constructor. The "Wait for Stop or Abort" Event Structure, shown below, will wait forever and never time out.
The symptom of this is that if the user's main VI is aborted (e.g. hard stop or exits without stopping/destroying the SMOs) then the SMO's will remain locked, due to the fact that there are still running Process VIs. The only way to unlock your SMOs is to close the projects or restart LabVIEW.
A solution, would be for the process to respond to the invalid refnums and stop waiting.
However, this solution of simply finishing waiting, will not stop all the child processes. And, unfortunately, the AbortProcesses and StopProcesses events are now invalid (because they've been garbage collected), so there's no good way to message into the child process loops.
Because we can't message into the child process loops (and their DVRs will the SMO's data are gone too), there's probably not a lot of point to try to gracefully exit. So, a reasonable solution is to kill the zombie is to shoot it in the brain, i.e. call the Stop function primitive.
This works well (at least it's easier for users than closing their project or restarting LabVIEW).
One improvement that could be made to this solution would be, instead of polling the reference validity, would be to wait in an event-driven fashion. My recommendation would be to create a single-element queue in the SMO constructor and wait on this queue (to go invalid) in parallel to the "Wait for Stop or Abort" Event Structure.
Here's a possible event-driven solution (that does not require polling the DVR):
I'm curious what others think about this.
The setting in "Register Process.vi" works incorrectly, while doing the inheritence.
I set "forces self to run" in the parent process to false, and set "parent call" of the child process to "does not request parent to run", but the parent always runs (it should goes to exiting case in state "Process: Sync", am I right?)
It seems the problem is in "WaitOnProcessSync.vi". Shall we check whether the process can run in the default case, just as what we do in case "Started" ?
When a SMO declares a static dependency, it injects its configuration manager as part of the inheritable attributes it shares with the SMOs it is taking ownership. It would be useful to provide a mean for a caller/owner to preset some configuration for its dependencies at the time of creation.
Describe the solution you'd like
Configuration managers can be injected at object creation, but the namespace for the key values pairs is normally determined by the object itself, not the caller. There could be a protected method for the owner to be able to preset the dependency's config, in its future namespace. When the dependency is started later on, the configuration manager set at creation time will be merged with the parent's configuration manager.
It results that if the caller has injected a "cache" config manager, it will be superseded by any specific non-cache managers, but object will default to cached config if key value pairs are not found in any other managers.
If the caller injected a non-cache manager, it will supersede whatever managers inherited from the caller, but cycle through all the collection of managers until it finds values for key value pairs in its own configuration.
overrides of enumerateStaticDependencies might be used to create and configure a static dependency, which would get recreated during cleaning steps for dynamically launched dependencies.
Fix: remove need to call the enumeration on dependency stopped event (Clean Dependencies)
A dependency can terminate itself when the work is done and notify its dependent.
Dependent receives a "Dependency Stopped" event but does not destroy the reference unless it explicitly called for its dependency to stop. The use case that needs to be covered is:
ex:
A starts B and C.
B then depends on C.
A stops... which destroys C before stopping B.
B errors out with 1556 error, even though it is not responsible for B's lifetime.
Because dependencies are created and started in onStart() method of a caller subsystem, they should be stopped and destroyed in the symmetrical onStopped() method of the same caller. They are currently stopped and destroyed in the onStop() method, which can generate errors in the caller's process when using a dependency, either through polling or concurrent query.
Right now, the Destroy method will call Abort. It would be nice if it checks if the SMO is running and gracefully calls Stop before Aborting/Destroying.
Hi!
I've just started using JKI's SMO and it seems the editor is seriously struggling when loading the SMO templates (it takes almost 15-30secs to finish loading them). I dared to venture into to source code and found out that the reason is the LVClass.Open invoke node. Is there really no better way to get the class into memory and receive a reference to it? The "Get LV class default value" loads the class into memory seemingly faster but i have no idea how to get a reference to the Class it loaded. I switched off the loading of the classes to get the desciption and the loading time dropped to 5 secs. As all references are closed when opening the editor again, i have to wait again. I love the tool by the way but the 15-30sec waiting time was killing me.
Thank you!
-Imre
It's been reported that restarting a process when the core process is set to not force itself to run would cause the process not to run the second time around.
The issue is that the flag "Allow Parent to Run" is not reset after a process terminates, if it is not destroyed.
Given this process configuration:
With this type of sequence calls:
The process would not run on the 2nd iteration.
The fix is to reset the flag in the SMO:Process.vi after the process terminates (synch'ed):
A common feature that we reimplement in any framework is configuration management.
To keep a lightweight base class and maximum flexibility, add a configuration management interface for which developers can inject their own configuration manager class.
Features of the configuration management implementation should be:
Are there more requirements that are "must-have"?
package build crashes LV2013SP1. (Image.cpp at line 13855, no lvfailure log found in LVData)
Prevents releasing 1.4.0. Will check with JKI (VIPM) and NI for cause of crash.
Arguments array is translated wrongly.
Instead of ?key=value&key2=value2
we get ?key&value=key2&value2
Attributes should be extended to have scope.
Private would be reserved for framework use.
Protected to be reserved for self use.
Public would be reserved for for self use and read-only access for other instances.
Name would be used as part of the migration to variant attributes, but also to manage unicity when declared.
To keep backward compatibility, class name to default to Short Class Name unless noted otherwise by developer. (optionally settable with attributes constructor)
Currently, Facade Calls can only be performed on pre-allocated clones which are never reserved for execution. There are methods to call static VIs remotely even when reserved for execution. Implement and test.
Discovering public API at SMO registration would allow to generically decouple views from active processes.
If the parent onStopped method is not called, the state of the SMO is never set to stopped. As a result, an SMO will try to be stopped twice. Errors will likely bet generated on the second attempt since references have already been destroyed.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.