generated from Foso/KotlinCompilerPluginExample
-
Notifications
You must be signed in to change notification settings - Fork 1
Open
Description
The idea behind the annotations is neat, but they are much stricter than makes sense, which requires even correct usage to add a lot of suppressions.
@Cacheis not a valid abstraction in a purity checker. Instead, this concept should be replaced with@GuardedByor some sort of@ThreadSafeObjectannotation for properties or parameters.- I should be able to mark a class as
@Immutableas well. @InternalStatedoes not make sense in a multi-threaded context. It needs to be replaced with@GuardedByor some sort of@ThreadSafeObject.@LocalStateshould not be needed. Any qualification should be enforced by properties that locals are assigned to, or by parameters that it's passed as. This is especially problematic for values created in a more complex expression, like builders. Right now, I have to create a builder, assign it to a@LocalState, and THEN use it to build. This is also especially problematic for caught exceptions, which appear impossible to annotate with any of these annotations, or even suppressed, without suppressing the entire method.- There are a many methods in the Java library, and a LOT of methods in Kotlin, that are not marked as
@Pureor@Readonlythat ought to be. Especially in the area of collections/sequences/streams/iterators:
"java.util.stream.StreamSupport.longStream",
"java.util.stream.LongStream.parallel",
"java.util.LinkedHashMap.forEach",
"java.util.LinkedHashMap.computeIfAbsent",
"kotlin.LongArray.get",
"kotlin.LongArray.iterator",
"kotlin.collections.copyOf", *
"kotlin.collections.copyInto", *
"kotlin.collections.firstNotNullOfOrNull",
"kotlin.collections.slice",
"kotlin.collections.IntIterator.hasNext",
"kotlin.collections.IntIterator.next",
"kotlin.sequences.firstNotNullOfOrNull",
"kotlin.sequences.minWithOrNull",
"kotlin.collections.copyInto", * - Note that three of those modify the parameter. Which reveals that the library needs an annotation for function parameters that signifies that the method mutates the parameter, but does not expose it outside of the method, so it doesn't violate purity when called on local or
@GuardedByvariables, or Thead-Safe properties. Very similar to the current@LocalStateactually. Suggested name:@Mutates. - My experience is that when devs think of a class as thread-safe, they tend to stop thinking about thread safety, and write code that assumes the data does not mutate in parallel. (The iconic example is ConcurrentHashMap) So this is why ThreadSafe annotations like
@Cacheand@InternalStatehave been problematic. I think this might be what to aim for:- Methods can be marked as either
@ThreadSafeUnconditionalWrite(ex:AtomicInt#clear) or@ThreadSafeCas(ex:AtomicInt#compareAndSwap) or@Readonly(ex:AtomicInt#get). - A method that calls a
@ThreadSafeXmethod should itself be marked@ThreadSafeRead("property_or_parameter_name")or @ThreadSafeWrite("property_or_parameter_name"). - A method that calls a
@ThreadSafeUnconditionalWritemethod may not call any other methods on that object BEFORE the call, even if those methods are@ThreadSafeX. - (Longer term) A method that calls a
@ThreadSafeXmethod may not call any other@ThreadSafeXmethods for that property, nor onthis. - These rules do not guarantee correctness, but I believe they'll make it more obvious to developers how thread safe code should look. And discourage ignoring thread safety simply because something is a
@Cacheor@InternalState.
- Methods can be marked as either
Examples:
class WorkerAutomation {
@ThreadSafeObject private val tileRankings = HashMap<Tile, TileImprovementRank>()
@ThreadSafeRead("tileRankings")
private fun startWorkOnCurrentTile(unit: MapUnit) {
val currentTile = unit.currentTile
val tileRanking = tileRankings[currentTile]!! // allowed because it only calls @Readonly
}
@Readonly
@ThreadSafeWrite("tileRankings")
fun getBasePriority(tile: Tile, unit: MapUnit): UnitPriority {
val unitSpecificPriority = 2 - (tile.aerialDistanceTo(unit.getTile()) / 2.0f).coerceIn(0f, 2f) * 3
// if (tileRankings.containsKey(tile))
// return tileRankings[tile]!!.tilePriority + unitSpecificPriority // error: multiple operations on ThreadSafe
val ranking= tileRankings.compute(tile, {tile2,ranking-> // instead: replace with one @CompareAndSwap call
if (ranking != null) return ranking
... // most of existing body here
}
return UnitPriority(ranking, unitSpecificPriority)
}
@Readonly
private fun getImprovementPriority(tile: Tile, unit: MapUnit, localUniqueCache: LocalUniqueCache): ImprovementPriotity {
val unitPriority = getBasePriority(tile, unit)
// @LocalState val rank = tileRankings[tile] // error: multiple operations on ThreadSafe
val rank = unitPriority.ranking
... // reset of existing body here
}
@Readonly
private fun tileHasWorkToDo(tile: Tile, unit: MapUnit, localUniqueCache: LocalUniqueCache): Boolean {
// if (getImprovementPriority(tile, unit, localUniqueCache) <= 0) return false
// if (!(tileRankings[tile]!!.bestImprovement != null || tileRankings[tile]!!.repairImprovment!!))
// error: multiple operations on ThreadSafe
val improvementPriority= getImprovementPriority(tile, unit, localUniqueCache)
if (improvementPriority.priority <= 0) return false
val rank =improvementPriority.ranking
val bestImprovement = improvementPriority.
if (!(rank.bestImprovement != null || rank.repairImprovment!!))
throw IllegalStateException("There was an improvementPriority > 0 and nothing to do")
return true
}
}
Metadata
Metadata
Assignees
Labels
No labels