Guard access by lock in Kotlin

GuardedByLock a simple helper to make sure variable is only accessed with lock

A topic of ownership is better covered in Rust Language. Still, such problems are not solved well enough in the JVM world. Let’s consider a trivial shared state access example

  private val lock = ReentrantLock()
  private var value = 42
  
  fun method(v : Int) {
    lock.lock()
    try {
      updateStateWithNewValue(v)
    } finally {
      lock.unlock()
    }
  }

We all know the best state is no state. And we tend to decouple tricky things. But of course, there are some places in programs where one finally needs to deal with a state. In reality, it can easily be a complicated state with several different locks in one object.

The common problem here is to make sure fields are accessed with correct locks taken. It is tricky to ensure in Java. It is double tricky to ensure future changes will not break the contract.

JSR-305 and GuardedBy annotations are created to make a binding between state and guards. But those are only to help an IDE to be smarter, those annotations will not turn a mistake into a compilation error.

I’m looking the way to make a compiler to ensure the access is correct.

Java Style solution

In Java, this can be done in the following way. Create a function runWithLock and make this function accept an interface with a method like run(State s). If necessary a generics can be added to simplify usages.

To implement that one need to extract a state object (which I call State). Happy Java 1.8 users may benefit from Lambda syntax. But the approach itself requires a creation of tiny objects (for lambdas, in most cases) on every call. Such overhead is small, but one may not like it.

An example call code would look like

  class State { /*...*/ }
  void runWithLock(Consumer<State> s) { /*...*/ }


  void main() {
    runWithLock((s) -> { s.value = 5;  });
  }

Let’s try avoid lambdas overhead and to yield a bit better syntax.

Kotlin Style solution

Consider the following implementation in Kotlin

class GuardedByLock<out L: Lock, out T>(
        val lock: L,
        val state: T
) {
  inline fun <Y> runWithLock(action: T.() -> Y) = lock.withLock { state.action() }
}

The usage is as follows:

   class State(var value: Int = 42)
   val lock = GuardedByLock(ReentrantLock(), State())
   
   fun main() {
     lock.runWithLock { value = 5 }
   }  

Let’s take a look closer to the runWithLock call. Here { and } are used to declare an anonymous extension function of type State.() -> Y (Y is a generic type parameter). Extension function means that in the body of the function we have this keyword pointing to State object instance. And the part value = 5 means we assign the value to State object property/field.

The inline keyword here makes Kotlin compiler to inline the function body to avoid creating an anonymous function in the compiled code. You may refer to the previous post for bytecode listing of inline function calls.

With this approach, we avoid creation anonymous classes for Lambdas from one hand. From the other hand, the Kotlin compiler is in charge of checking a State object instance in only accessible after necessary locks are held.

Finally, the bytecode for the main function is the following. Note. I use IntelliJ IDEA 2017.1 EAP with Kotlin 1.0.6 plugin. The generated bytecode may change with a future version of tools.

  // access flags 0x19
  public final static main()V
    TRYCATCHBLOCK L0 L1 L2 null
    TRYCATCHBLOCK L2 L3 L2 null
   L4
    LINENUMBER 27 L4
    GETSTATIC GlKt.lock : LGuardedByLock;
    ASTORE 0
    NOP
   L5
    LINENUMBER 30 L5
    ALOAD 0
    INVOKEVIRTUAL GuardedByLock.getLock ()Ljava/util/concurrent/locks/Lock;
    ASTORE 1
    NOP
   L6
    ALOAD 1
    INVOKEINTERFACE java/util/concurrent/locks/Lock.lock ()V
   L7
   L0
    NOP
   L8
    NOP
   L9
    LINENUMBER 30 L9
    ALOAD 0
    INVOKEVIRTUAL GuardedByLock.getState ()Ljava/lang/Object;
    CHECKCAST State
    ASTORE 2
    NOP
   L10
    LINENUMBER 27 L10
    ALOAD 2
    ICONST_5
    INVOKEVIRTUAL State.setValue (I)V
   L11
   L12
    GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit;
   L13
   L14
    LINENUMBER 30 L14
    ASTORE 2
   L1
    ALOAD 1
    INVOKEINTERFACE java/util/concurrent/locks/Lock.unlock ()V
    ALOAD 2
    GOTO L15
   L2
    ASTORE 2
   L3
    ALOAD 1
    INVOKEINTERFACE java/util/concurrent/locks/Lock.unlock ()V
    ALOAD 2
    ATHROW
   L15
    LINENUMBER 30 L15
   L16
    POP
   L17
    LINENUMBER 28 L17
    RETURN
   L18
    LOCALVARIABLE $receiver LState; L10 L12 2
    LOCALVARIABLE $i$a$1$runWithLock I L10 L12 3
    LOCALVARIABLE $i$a$1$withLock I L9 L14 4
    LOCALVARIABLE this_$iv LGuardedByLock; L5 L16 0
    LOCALVARIABLE $i$f$runWithLock I L5 L16 5
    MAXSTACK = 2
    MAXLOCALS = 6

As we see from the code, there is NO anonymous class creation.A call to GuardedByLock#runWithLock was inlined into the main function body, meaning less overhead (who’d measure it :).

Conclusion

In the post, I presented GuardedByLock class in Kotlin. Using it in your project may help to correctly isolate state and make Kotlin complier checking all possible violations for you. The only cost is 2 additional object instances: one for GuardedByLock, and the other one for the State.

comments powered by Disqus