Can SFCs cause the same type of memory leaks as globals?

I am working to replace a feature that currently leverages the dark arts of storing custom classes in globals (I know, this is illegal, but technically possible with enough extra work)

The amount of leak management code required for that to work is a pain, but it technically is working at this point in a production environment without leaks. (Don't ask how, as if you need to ask how you shouldn't be doing it)

I am evaluating migrating a big piece of this functionality to leverage SFCs instead of my singleton class running in globals. Seeing as they are gateway scoped, can hold scripts, and solve a few other problems my class has now anyway.

However this only resolves the parent wrapper class that gets initialized, but not any classes that are attached to that one for smaller items. So my question is, if an SFC is holding onto an instance of a custom class or custom code in general, and the project scripts restart, will that custom class still point to an old interpreter and cause the same type of memory leak? Or will that get closed up when the SFC completes?

What are the rules around how to avoid memory leaks with SFCs in this regards?

I would expect so, but I avoid SFCs. You're on your own.

Out of curiosity, why avoid them?

I also have in the past, but I have seen this very specific use case that seems to fit very well. It only took me about a year after @PGriffith and Carl Gould told me "So why didn't you use SFCs for that feature?" to get a chance to dig deep lol

Because you still have to write procedural code (or ladder in a PLC), but it is now broken up over many blocks/steps/transitions, and the information about what gets run when is not present when looking at those pieces. You must look at the chart.

When you write conventional procedural equivalents, the branching that corresponds to SFC paths is explicit, and typically in the same code space as the rest of the code.

SFC development, to me, is a nightmare of flipping through all the pieces once the complexity exceeds what fits in a human's mental map. Pure procedural code tends to have a less complex hierarchy for any given solution's complexity.

IMNSHO.

If you ask me to work on them, I'm happy to rip them out and replace with neat code.

1 Like

So, I understand this logic. However I think maybe the scenario in which they are being used might be more relevant here

Essentially the application has an abstract concept of "Events", where events can have a couple popups show up with inputs, confirmations, send out API calls and wait for responses, etc.

A user may "Start an event" (Which is signing off a task) and it do the following:

  • Start Event
  • Open a popup to confirm you're ready to start?
  • Open a different popup to get a few inputs
  • Based off those inputs, maybe make an API cal, or skip it
  • Based off the response of that API call, maybe put something in the database
  • Tell the user that either everything succeeded or it failed

That isa a pretty small example, but it happens in many many different ways, with a different path, etc.

Its also relevant that this process needs to go back and forth between Perspective and the gateway as it goes, with specific things happening on the gateway, and chaining together this process through Perspective.

Building a class to manage this event, and its flow worked great, except for needing to keep these classes living in parallel to Perspective.

Hence the idea of switching to an SFC to manage event process and flow, and have that make calls back and forth with Perspective to handle the user side of things

I think SFCs use Java serialization for persistence. So, once persisted I don't think you'd be subject to memory leaks, since you'd get a true 'new' instance of the object... maybe.

Sounds like you need my pageVarMap() for the UI. Perhaps a globalVarMap() when the UI hands off to the gateway. Whenever handing to the gateway, include the origin session and page ids, so you can retrieve and trigger the right pageVarMap on completion. And clean up if it has disappeared. (Because those are weakly referenced.)

I'd call these workflows, not events, fwiw.

But jython classes are not serializable. (Strictly speaking, not deserializable after their origin interpreter dies.)

I thought at runtime every class ends up represented as a PyObject, which does implement Serializable.

I definitely wouldn't rely on Java serialization; while I also don't necessarily like SFCs I think they're a totally valid tool if you understand the tradeoffs. I would recommend the same general advice as given for use of system.util.globals - store 'native' data collections and 'rehydrate' your classes as needed. I've honestly never gotten a chance to see anyone's full class-based-inside-Ignition approach to state modeling to see it make sense to me.

1 Like

Yeah, it lies. Jython's underlying java class names for user-defined classes is not repeatable, and therefore fail when trying to read them back in (whether in a later process, or different interpreter instance.).

Jython foundation classes are themselves serializable, but derived classes are not.

1 Like

So because each "workflow" has multiple types of paths and steps, I figured an SFC is the best way to represent them, as they are similar to the process maps used to define them.

Here is an example process flow, that has everything from user inputs, to updating the database, etc. That ends up serializing into the database as a simple object.

Current flow:

  1. User presses the "Start transfer" button
  2. Modules.Transfers.StartTransfer(relavant_params).start()
  3. That does some stuff, to determine if its ready
  4. That creates the SFC (and can pass a serialized version of the class if required, but ideally passes a reference to the instance itself)
  5. SFC goes back and forth to the user regarding confirmation, and inputs.
    • This manifests as system.util.sendMessage calls that are caught by the session, and then system.sfc.setVariable calls that unblock transitions in the SFC.
  6. Eventually the SFC completes, and then calls Modules.Transfers.StartTransfer(relavant_params).save(relavant_data_from_process)
    • (relevant_params can be serialized if required)

I am not sure of any other clean way to manage the back and forth between the screen and something that is going to execute and block threads for things like API calls, database updates, etc.

For clarification, this is one simple workflow in an application that contains hundreds of these workflows for various types of events, so I need to implement things scalably.

1 Like

See, I'm totally on board with SFCs for this use case.

But couldn't you just store whatever state needs to be kept track of between steps as...plain data structures in the SFC?

My sticking point is what clarity making custom Python classes gets you. Validation feels like it should just be a pure function; given XYZ as inputs, return a tuple (or whatever) and then store that, or if it failed, abort the operation/GOTO 10/whatever.

Again: This isn't me trying to say these approaches are wrong! I just haven't had the chance to be 'in the trenches' enough to know why they're used. Talk to my boss about it :smile:

1 Like

Only benefit would be that I could pass the TransferEvent instance into the SFC and then not ever need to re-initialize it, however I could just serialize it and reinitialize it at the end when its done and I need it to do something more complex. The reason these are classes and not just functions is mostly to provide organization, and creating abstract classes that actually perform generic methods against each subclass. The SFC is really only the execution of this workflow, the class structure itself is helping with things like registering events in submodules and what should happen on what order, etc. etc.

Like I said, your boss and you were the ones that told me to look into SFCs at ICC last year :wink:

Again: Not in the trenches, but this smells a lot like "multiple sources of truth for state". That's generally a rough time, maintenance wise. I think your options are to go 'all in' on SFCs, accept the pain of this partial state transfer weirdness, or go all-in on scripting and drop SFCs.