Understanding Synchronization and Data Access with system.util.getGlobals

Hello,

I'm seeking insights on backing storage and synchronization primitives for data accessed via the system-util-getGlobals function. While it seems promising for my goals, I'm looking to better understand its underlying mechanisms to ensure correct implementation.

Background

I'm more experienced with Rust, so let me share a recent project as context. I had to manage mutable values maintained by their respective tasks running on threads within an asynchronous runtime, with a dedicated TUI thread for visualization. To achieve this, I utilized a Mutex as in the following minimal example:

/// Outer structure wrapping the inner structure in a thread-safe `Mutex`.
struct Data {
    inner: Arc<Mutex<Inner>>,
}

/// Inner structure containing recorded data.
struct Inner {
    values: Vec<u64>,
}

I enabled the TUI thread to access the data within a locked scope for its required duration (akin to how I imagine system.util.getGlobals could work):

impl Data {
    /// Access the recorded values inside a closure.
    fn with_values<F, T>(&self, function: F) -> T
    where
        F: FnOnce(&[u64]) -> T,
    {
        let lock = self.inner.lock().expect("acquired mutex guard");
        function(&lock.values)
    }
}

Questions

  1. Does the JVM utilize a synchronization primitive similar to Rust's Mutex for backing data accessed via system.util.getGlobals? Is this synchronization applied to the entire structure or sharded by key?
  2. Should Mutex contention be a concern when utilizing system.util.getGlobals?
  3. Are there scenarios where using system.util.getGlobals could potentially lead to deadlocks?

It's basically just a ConcurrentMap. It's safe for access by multiple threads in terms of visibility, but there is no mutual exclusion happening.

1 Like
  1. No. Jython collections, sets, and dictionaries are all based on java "Concurrent" underlying classes, so they are thread-safe, but algorithms built with them are not.

  2. Yes. Java is highly multithreaded, so any thread waiting on a mutex could block actions that do not expect to wait (like the tag event thread pool).

  3. Not in and of itself, no.

  4. (Yeah, you didn't have "4".) The object returned from system.util.getGlobals() also holds top-level variables in gateway events. Yes, shared across the entire gateway. Access to those was/is part of its design. I created alternatives (now system.util.globalVarMap() in my Integration Toolkit) to avoid this particular complication. (There used to be more problems.)

Careful use of .setdefault on Jython dictionaries, and .putIfAbsent() on ConcurrentHashMaps can reduce the need for mutexes or other locks. Consider this topic:

Related: If you always use one-liners in your gateway events that delegate to project library scripts, then you aren't creating any top-level variables that could clash with other residents of system.util.getGlobals().

1 Like

I agree on those implementation guidelines.

I thought 4 was implied but I appreciated the added clarification. I think Ignition is a fantastic product but I often see a lot of complicated scripting solutions that quickly go awry as the system they service scales.

I hope that clarifying posts like the ones you both made here and linked to lead to more thoughtful and performant solutions in the future.

Thank you both!

You might find this topic helpful, too:

1 Like

That link is really great thank you! Also hard agree on the point about using bounded channels to handle back pressure. The very same project I mentioned above uses an actor pattern, where an actor holds an IO resource, and its handle passes messages to the actor over a bounded queue. I'm happy to see that this is a pattern I can implement in Ignition as well.

1 Like