Eventual consistency?

Does the smartthings cloud run on a model of eventual consistency? I’m really confused about the runtime environment.

In the following simulator output is the debug output from a smartapp which is a bit like the ‘gentle wakeup’ app. Every 5 seconds, it is supposed to update the state.level by +5 percent, and set that level in the dimmer. Things seem ok until I start to look at the dimmer value via a subscription to the “level” attribute.

In the app:

  • “increment” updates the state.level and calls “setAllDimmers”
  • “checkLevel” outputs the state.level and the dimmer level
  • “setAllDimmers” sets the level on all the configured dimmers
21:09:01: debug in setAllDimmers: setting dimmer to level 15 21:09:01: debug in increment: calling setAllDimmers with level 15 21:09:01: debug in increment: state level is 15 21:09:07: debug in checkLevel: current state of Dimmer Switch is on: 15 21:09:07: debug in checkLevel: state level 10 21:08:56: debug in checkLevel: current state of Dimmer Switch is on: 10 21:08:56: debug in checkLevel: state level 10 21:08:52: trace Scheduling 'increment' for InstalledSmartApp: 9e7ebd73-309b-42a0-8d2f-85dc4868ef93 21:08:51: trace Deleting scheduled job 'increment' for InstalledSmartApp: 9e7ebd73-309b-42a0-8d2f-85dc4868ef93 21:08:51: debug in increment: calling runIn with duration 5 and function 'increment' 21:08:50: debug in setAllDimmers: setting dimmer to level 10 21:08:50: debug in increment: calling setAllDimmers with level 10 21:08:50: debug in increment: state level is 10

In log order:

  • state level is set to 10, dimmer is set to 10
  • subscription fires, and state and dimmer are 10 (so far so good)
  • subscription fires, state is 10, dimmer is 15 (huh?)
  • state level is set to 15, dimmer is set to 15

In time order:

  • state level is set to 10, dimmer is set to 10
  • subscription fires, and state and dimmer are 10
  • state level is set to 15, dimmer is set to 15
  • subscription fires, state is 10, dimmer is 15 (huh?)

Neither of these event orderings makes sense to me. Does anyone have any insight?

Here’s the code, if anyone is interested:

preferences {
    section("When I touch the app, turn on slowly...") {
        input "dimmers", "capability.switchLevel", title: "Which lights to slowly raise?", description: "Tap to select lights", multiple: true
        input "startLevel", "number", title: "What level should the lights start at?", description: "0 (default)", required: false
        input "stepSize", "number", title: "What percent should the lights change?", description: "5 (default)", required: false
        input "numSteps", "number", title: "How many steps should be taken?", description: "10 (default)", required: false
        input "stepDuration", "number", title: "How long should each step take in seconds?", description: "5 (default)", required: false
    }
}

def installed() {
    defaultState()
}

def updated() {
    unsubscribe()
    defaultState()
}

def defaultState() {
    state.startLevel = startLevel ?: 0
    state.stepSize = stepSize ?: 5
    state.numSteps = numSteps ?: 10
    state.stepDuration = stepDuration ?: 5
    state.level = state.startLevel
    state.endLevel = state.startLevel + (state.stepSize * state.numSteps)
    subscribe(app, appTouch)
    subscribe(dimmers, "level", checkLevel)
    debug "...state reset."
}

def checkLevel(event) {
    debug "in checkLevel: state level ${state.level}"
    // Check that all the dimmers are at the desired state
    dimmers.each { dimmer ->
        int current_level = dimmer.currentValue("level")
        String status = dimmer.currentValue("switch")
        debug "in checkLevel: current state of ${dimmer.name} is ${status}: ${current_level}"
    }
}

def appTouch(event) {
    debug event
    debug "app was touched."
    debug "Initiating sunrise..."
    increment()
}

def increment() {
    state.level = state.level + state.stepSize
    debug "in increment: state level is ${state.level}"
    if (state.level > state.endLevel) {
        debug "done"
        return
    }
    debug "in increment: calling setAllDimmers with level ${state.level}"
    setAllDimmers(state.level)
    debug "in increment: calling runIn with duration ${state.stepDuration} and function 'increment'"
    runIn(state.stepDuration, "increment")
}

def setAllDimmers(level) {
    dimmers.each { dimmer ->
        debug "in setAllDimmers: setting dimmer to level ${level}"
        dimmer.setLevel(level)
    }
}

private debug(message) {
    log.debug message
}

Yes, the SmartThings datastore as you suspected, is eventually consistent. This gives better read throughput, but does mean that if you increment a value and then immediately try to read it right away you may get the old one. However, the end value will be correct as in your example on subsequent reads. If you want to check the value immediately after updating a better option may be to increment the level with your stepDuration and store that to a variable inside your method, then have your logic check against that, setting your state.level equal to that if your conditions aren’t met and you need to schedule it again.

I’m skeptical about the ‘better read throughput’ rationale for eventual consistency. We’re literally talking about 1-2 bits per second of data that I’m attempting to read. I’m feeling the pain with no gain here.

In any case, the goal is to loop and increment, but if at any time an outside command changes the value of the dimmer, exit the loop. This is why I’m setting the level in the state and checking it when the level changes in the dimmer.

When you suggest storing a variable inside my method and then checking against that instead of checking against ‘state’, how do you propose to implement the check? I only see two options, neither of which will work for the use case:

  1. check the current level immediately. This is not what I want - I want to know when the light has changed, so I can tell if it changed to my level or to a different level (and exit in the latter case). Most of the time, an immediate check will be too fast.
  2. check against the level I set via a subscription. This is what I want, but subscriptions accept neither arguments nor closures, and I’ve discovered that I cannot depend on ‘state’ state to be consistent.

Do you have another way in mind? It’d be awesome if you did. Or if you see another way to ‘let go’ of a device if another smartapp or the user butts in during execution.

1 Like

A quick update - I recently discovered the ‘isPhysical’ property of an event, which lets me know someone actually hit the switch, which is great. I still need a way to know if another app has modified the device, though, so I can avoid a scenario with duelling apps. The only way I see to do that is still to check, when a device changes, if the change was the one I commanded from this app.

With an inconsistent data store (so I can’t depend on global state), and no way to pass values to subscribed functions, I still don’t see a way to accomplish this. If anyone does, I’d love to hear it. (that’s not sarcasm, I’d really be happy to get a solution working).

I apologize for not getting back to you sooner. I had missed your last response. Good news is that SmartThings has just implemented a new feature that should make what you’re trying to do possible. As you have found out doing something like state.level is an eventually consistent read. However, you can now do atomicState.level. This will perform a strongly consistent read to our datastore and should allow you to do the global atomic operations that you are looking for. Let me know if you are still having issues after switching to use that. A good place to start may be just changing all of you state references to atomicState and see if that yields the behavior you are looking for.

1 Like