atomicState not working

(Anders Heie) #1

I had some state inconsistencies (too complicated to explain, and doesn’t really matter), so I decided to try and switch to atomicState instead. I simply replaced “state” with “atomicState” through my application (Not device), as suggested in the documentation.

To illustrate the problem, try this code assuming you have some switches:

atomicState.switches = [:]

switches.each { 
    // Set ready state for each switch
    atomicState.switches["$"] = "ready"

atomicState.switches.each {
	log.debug "$it.key = $it.value" 

This works for state, but not for atomicState.

(Anders Heie) #2

Has anyone ever used this?

( - Make your home your butler!) #3

hmm same issue here, am trying to use atomicState to track something which can change very rapidly due to multiple events being fired. I can’t seem to use an array/list with atomicState.

@Jim any insight into restrictions on atomicState (this is with a SmartApp)

( - Make your home your butler!) #4

EDIT: Sorry, it doesn’t work even with integers and strings. This is a simple array like:


atomicState.locks = []


atomicState.locks.add( // is an integer or when using lock object itself it through an exception

atomicState.locks is always empty

(sidjohn1) #5

The docs cover how to with state and maps. I personally haven’t tried atomicstate maps, but it’s worth a shot

(Chris) #6

I believe that the atomicState is only persisted when something on the root object changes. Since this has to happen immediately, and it could be expensive to watch every single child property/object. So for instance in your situation you need to reassign the property on atomicState to the value you changed it to.

// Initialise
atomicState.locks = []

// Event handler execution or something

def locks = atomicState.locks


atomicState.locks = locks

This is how I got it to work in a SmartApp I was working on that needed to aggregate events into 2 second chunks - I would store the events in atomicState until a schedule runs to process them. using atomicState was necessary because sometimes things wouldn’t persist properly because they were being overwritten by another execution running concurrently.

This is why the docs use:

atomicState.counter = atomicState.counter + 1

As it is retrieving the original value, modifying it and reassigning it back to atomicState, but this isn’t obvious and should probably be made clear in the docs.

This is necessary on state because at the end of execution the entire object is persisted regardless, it doesn’t need a trigger like atomicState.

( - Make your home your butler!) #7

Genius @Kriskit - thanks that worked perfectly. @Jim I also recommend that the docs be updated to clarify the same. (BTW there is no documentation for atomicState under API Documentation -> Smart App (state is there but there is no atomicState)

However I do have a question, the whole point of atomicState is to have it happen in a single instruction to avoid race conditions. By assigning to an object, modifying and reassigning back to atomicState doesn’t that defeat the purpose?

( - Make your home your butler!) #8

Did something change with atomicState? Suddenly it no longer works. The code was working till a few hours ago and now suddenly I’m getting a null returned from atomicState.

( - Make your home your butler!) #9

So it started when I decided to use 3 atomicState variables. All of a sudden it just stopped! I would only get null’s back from all 3 atomicState variables (EXACT same copy paste code of the working code posted above by @Kriskit which I verified was working), I just changed the names.

Then I went back and reverted to the old code with just 1 atomicState variable (which was working). Now even that returns a null!!

I can’t explain it, working code no longer works after introducing 3 atomicState variables. Anyone any clue? This is very funky!

( - Make your home your butler!) #10

This is what I’m using and it’s not working:
In installed()/udpated():

atomicState.immediateLocks = []

Later in the code:

def immediatelocks = atomicState.immediateLocks
if (!immediatelocks.contains( {
            atomicState.immediateLocks = immediatelocks

And it errors out on the third line:

12:37:23 AM: error java.lang.NullPointerException: Cannot invoke method contains() on null object @ line 463

If I remove the check for the contains and just do what I did yesterday, it errors out the next line:

12:44:26 AM: error java.lang.NullPointerException: Cannot invoke method add() on null object @ line 465

What could I be doing wrong? (this was working yesterday)

( - Make your home your butler!) #11

So all I could figure out is that atomicState does not save the state of an empty object (e.g. empty array), it discards it. Unlike state where it does save the state of an empty object, hence yesterday in my app I started with a state object and then moved to a atomicState object which is why it probably worked, today starting afresh I didnt’ because when you allocate an empty list to the atomicState ibject, it discards it so it keeps returning null.
The only workaround for this problem is to reallocate a new list if the atomicState object is null (empty).

def immediatelocks = atomicState.immediateLocks ?: []

This works fine. @Jim if this truly is the case, I would recommend that the documentation be updated to reflect it.

(James) #12

Hello Everyone,
Just throwing my 2cents in. I noticed that the atomicState object does not handle objectRefs very well. Maps in particular. Just like the thread creator I was trying to use a map and noticed my changes are not sticking so i did the work around where I temporarily cache it out and throw it back in when done.

But in hindsight, that means that you are no longer getting an atomic operation by default if you do something like this.

get map
alter map
put map

If another trigger comes in at that time it will get a version of a map that is not update and then you will have some issues. Something that is not fast but slightly better would be

atomicState.lockFlag = true
get Map
alter Map
put Map
atomicState.lockFlag = false

Now that is not an ideal solution, but that’s one solution.
Another solution would be to flatten your object straight into top level atomicState so that it sees changes.

(Jeremy Mickelson) #13

Without some sort of atomic check and set operation it seems like the atomicState property is useless. Even if you implement a mutex like so:

atomicState.lockFlag = true
get Map
alter Map
put Map
atomicState.lockFlag = false

you could still get two threads who both read the lockFlag as true and then enter the critical section. Without atomic operations atomic state is useless and doesn’t solve the concurrency problem. We need something like java.util.concurrent.atomic.AtomicBoolean's compareAndSet method to make it work.

We just need someway to manage concurrency for these apps that need to batch events to send to external systems, otherwise you can’t manage the buffer. We need either atomic operations on AtomicState booleans so we can implement our own locks, or native support for locks, or critical sections (synchronized blocks). Really anything to manage concurrency would work.

( - Make your home your butler!) #14

@Jim is atomicState broken or am I doing something wrong here? This just won’t work

    atomicState.queuedActions = []
    def queue = atomicState.queuedActions ?: []
    queue << [function: "updateCameraImage", parameter: "", child: child]
    atomicState.queuedActions = queue

It just craps out on this line:

    atomicState.queuedActions = queue

Just refuses to execute it. If I comment out the line it goes one but if I don’t it just hangs there. I’m using a Service Manager SmartApp if that makes any difference.

Is this broken or am I doing something wrong?

( - Make your home your butler!) #15

@Jim heck it dies on this line!

atomicState.queuedActions = [[function: “updateCameraImage”, parameter: “”, child: child]]

This is straight from the documentation. Broken or misused?

EDIT: The same command with state instead of atomicState works perfectly!

( co-founder Terry @ActionTiles; GitHub: @cosmicpuppy) #16

SmartThings might be able to get a detailed stack trace from their servers. You may need someone live to get it right while it’s hanging or force it to throw an exception or something.

atomicState has to write immediately to permanent storage … my guess is that there’s a bug serializing the data. Replace the line with some sort of really simple data structure and see if it works, and then work your way up?

The other possibility is that you’re running into deadlock somehow? atomicState must have a mutex record lock around it, right? … to prevent the same data block from being overwritten concurrently by another processs … of course, I presume you’re testing with only one concurrent process; just grasping at possibilities here. There’s certainly enough complexity under the covers of atomicState that it’s prone for a bug to creep in that’s hard to test for.

( - Make your home your butler!) #17

you’re onto it, it just could be because I’m using the parent child relationship it’s causing a deadlock.
Well the good news is that I’ve given the code to reproduce it consistently I hope @jody.albritton @slagle or someone can check it out and let us know what’s going on.