Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Issue IDE warning if annotated member is not surrounded with a particular block

I have a data structure which has members that are not thread safe and the caller needs to lock the resource for reading and writing as appropriate. Here’s a minimal code sample:

class ExampleResource : LockableProjectItem {
    override val readWriteLock: ReadWriteLock = ReentrantReadWriteLock()

    @RequiresReadLock
    val nonThreadSafeMember: String = ""
}

interface LockableProjectItem {
    val readWriteLock: ReadWriteLock
}

fun <T : LockableProjectItem, Out> T.readLock(block: T.() -> Out): Out {
    try {
        readWriteLock.readLock().lock()
        return block(this)
    } finally {
        readWriteLock.readLock().unlock()
    }
}

fun <T : LockableProjectItem, Out> T.writeLock(block: T.() -> Out): Out {
    try {
        readWriteLock.writeLock().lock()
        return block(this)
    } finally {
        readWriteLock.writeLock().unlock()
    }
}

annotation class RequiresReadLock

A call ExampleResource.nonThreadSafeMember might then look like this:

val resource = ExampleResource()
val readResult = resource.readLock { nonThreadSafeMember }

To make sure that the caller is aware that the resource needs to be locked, I would like the IDE to issue a warning for any members that are annotated with @RequiresReadLock and are not surrounded with a readLock block. Is there any way to do this in IntelliJ without writing a custom plugin for the IDE?

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>Solution :

I think this is sort of a hack, but using context receivers might work. I don’t think they are intended to be used in this way though.

You can declare a dummy object to act as the context receiver, and add that as a context receiver to the property:

object ReadLock

class ExampleResource : LockableProjectItem {
    override val readWriteLock: ReadWriteLock = ReentrantReadWriteLock()

    // properties with context receivers cannot have a backing field, so we need to explicitly declare this
    private val nonThreadSafeMemberField: String = ""

    context(ReadLock)
    val nonThreadSafeMember: String
        get() = nonThreadSafeMemberField
}

Then in readLock, you pass the object:

fun <T : LockableProjectItem, Out> T.readLock(block: context(ReadLock) T.() -> Out): Out {
    try {
        readWriteLock.readLock().lock()
        return block(ReadLock, this)
    } finally {
        readWriteLock.readLock().unlock()
    }
}

Notes:

  • This will give you an error if you try to access nonThreadSafeMember without the context receiver:

    val resource = ExampleResource()
    val readResult = resource.nonThreadSafeMember //error
    
  • You can still access nonThreadSafeMember without acquiring a read lock by doing e.g.

    with(ReadLock) { // with(ReadLock) doesn't acquire the lock, just gets the context receiver
        resource.nonThreadSafeMember // no error
    }
    

    But it’s way harder to accidentally write something like this, which I think is what you are trying to prevent.

  • If you call another function inside readLock, and you want to access nonThreadSafeMember inside that function, you should mark that function with context(ReadLock) too. e.g.

    fun main() {
        val resource = ExampleResource()
        val readResult = resource.readLock {
            foo(this)
        }
    }
    
    context(ReadLock)
    fun foo(x: ExampleResource) {
        x.nonThreadSafeMember
    }
    

    The context receiver is propagated through.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading