FIR LC: fix light facade caching
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
plugins {
|
||||||
|
kotlin("jvm")
|
||||||
|
id("jps-compatible")
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":compiler:psi"))
|
||||||
|
implementation(intellijCoreDep()) { includeJars("intellij-core", rootProject = rootProject) }
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
"main" { projectDefault() }
|
||||||
|
"test" { none() }
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
explicitApi()
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package org.jetbrains.kotlin.analysis.utils.caches
|
||||||
|
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.psi.util.CachedValue
|
||||||
|
import com.intellij.psi.util.CachedValueProvider
|
||||||
|
import com.intellij.psi.util.CachedValuesManager
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
public inline operator fun <T> CachedValue<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
|
||||||
|
|
||||||
|
public inline fun <T> softCachedValue(
|
||||||
|
project: Project,
|
||||||
|
vararg dependencies: Any,
|
||||||
|
crossinline createValue: () -> T
|
||||||
|
): CachedValue<T> =
|
||||||
|
CachedValuesManager.getManager(project).createCachedValue {
|
||||||
|
CachedValueProvider.Result(
|
||||||
|
createValue(),
|
||||||
|
dependencies
|
||||||
|
)
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.analysis.utils.caches
|
||||||
|
|
||||||
|
import com.intellij.openapi.util.ModificationTracker
|
||||||
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
public class StrongRefModificationTrackerBasedCache<T> internal constructor(
|
||||||
|
private val dependencies: List<ModificationTracker>,
|
||||||
|
private val compute: () -> T,
|
||||||
|
) {
|
||||||
|
private val cached = AtomicReference<CachedValue<T>?>(null)
|
||||||
|
|
||||||
|
public operator fun getValue(thisRef: Any?, property: KProperty<*>): T = cached.updateAndGet { value ->
|
||||||
|
when {
|
||||||
|
value == null -> createNewCachedValue()
|
||||||
|
value.isUpToDate(dependencies) -> value
|
||||||
|
else -> createNewCachedValue()
|
||||||
|
}
|
||||||
|
}!!.value
|
||||||
|
|
||||||
|
private fun createNewCachedValue() = CachedValue(compute(), dependencies.map { it.modificationCount })
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CachedValue<T>(val value: T, val timestamps: List<Long>) {
|
||||||
|
fun isUpToDate(dependencies: List<ModificationTracker>): Boolean {
|
||||||
|
check(timestamps.size == dependencies.size)
|
||||||
|
for (i in timestamps.indices) {
|
||||||
|
if (dependencies[i].modificationCount != timestamps[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create modification tracker which will be invalidated when dependencies change.
|
||||||
|
* The cached value is hold on the strong reference.
|
||||||
|
* So, the value will not be garbage collected until modification tracker changes.
|
||||||
|
*/
|
||||||
|
public fun <T> strongCachedValue(
|
||||||
|
vararg dependencies: ModificationTracker,
|
||||||
|
compute: () -> T,
|
||||||
|
): StrongRefModificationTrackerBasedCache<T> = StrongRefModificationTrackerBasedCache(dependencies.toList(), compute)
|
||||||
@@ -22,6 +22,7 @@ dependencies {
|
|||||||
implementation(project(":compiler:ir.psi2ir"))
|
implementation(project(":compiler:ir.psi2ir"))
|
||||||
implementation(project(":compiler:fir:entrypoint"))
|
implementation(project(":compiler:fir:entrypoint"))
|
||||||
implementation(project(":analysis:analysis-api-providers"))
|
implementation(project(":analysis:analysis-api-providers"))
|
||||||
|
implementation(project(":analysis:analysis-internal-utils"))
|
||||||
|
|
||||||
api(intellijCoreDep()) { includeJars("intellij-core", "guava", rootProject = rootProject) }
|
api(intellijCoreDep()) { includeJars("intellij-core", "guava", rootProject = rootProject) }
|
||||||
|
|
||||||
|
|||||||
+2
-4
@@ -13,18 +13,16 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.api.FirModuleResolveState
|
|||||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.lazy.resolve.FirLazyDeclarationResolver
|
import org.jetbrains.kotlin.analysis.low.level.api.fir.lazy.resolve.FirLazyDeclarationResolver
|
||||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.FirIdeSession
|
import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.FirIdeSession
|
||||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.FirIdeSessionProviderStorage
|
import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.FirIdeSessionProviderStorage
|
||||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.cachedValue
|
|
||||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.getValue
|
|
||||||
import org.jetbrains.kotlin.analysis.project.structure.KtModule
|
import org.jetbrains.kotlin.analysis.project.structure.KtModule
|
||||||
import org.jetbrains.kotlin.analysis.project.structure.KtSourceModule
|
import org.jetbrains.kotlin.analysis.project.structure.KtSourceModule
|
||||||
import org.jetbrains.kotlin.analysis.providers.createProjectWideOutOfBlockModificationTracker
|
import org.jetbrains.kotlin.analysis.providers.createProjectWideOutOfBlockModificationTracker
|
||||||
|
import org.jetbrains.kotlin.analysis.utils.caches.strongCachedValue
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
internal class FirIdeResolveStateService(project: Project) {
|
internal class FirIdeResolveStateService(project: Project) {
|
||||||
private val sessionProviderStorage = FirIdeSessionProviderStorage(project)
|
private val sessionProviderStorage = FirIdeSessionProviderStorage(project)
|
||||||
|
|
||||||
private val stateCache by cachedValue(
|
private val stateCache by strongCachedValue(
|
||||||
project,
|
|
||||||
project.createProjectWideOutOfBlockModificationTracker(),
|
project.createProjectWideOutOfBlockModificationTracker(),
|
||||||
ProjectRootModificationTracker.getInstance(project),
|
ProjectRootModificationTracker.getInstance(project),
|
||||||
) {
|
) {
|
||||||
|
|||||||
+2
-2
@@ -16,16 +16,16 @@ import org.jetbrains.kotlin.fir.FirModuleData
|
|||||||
import org.jetbrains.kotlin.fir.moduleData
|
import org.jetbrains.kotlin.fir.moduleData
|
||||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.FirPhaseRunner
|
import org.jetbrains.kotlin.analysis.low.level.api.fir.FirPhaseRunner
|
||||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.addValueFor
|
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.addValueFor
|
||||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.*
|
|
||||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.executeWithoutPCE
|
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.executeWithoutPCE
|
||||||
import org.jetbrains.kotlin.analysis.project.structure.KtModule
|
import org.jetbrains.kotlin.analysis.project.structure.KtModule
|
||||||
import org.jetbrains.kotlin.analysis.project.structure.KtSourceModule
|
import org.jetbrains.kotlin.analysis.project.structure.KtSourceModule
|
||||||
|
import org.jetbrains.kotlin.analysis.utils.caches.*
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
class FirIdeSessionProviderStorage(val project: Project) {
|
class FirIdeSessionProviderStorage(val project: Project) {
|
||||||
private val sessionsCache = ConcurrentHashMap<KtSourceModule, FromModuleViewSessionCache>()
|
private val sessionsCache = ConcurrentHashMap<KtSourceModule, FromModuleViewSessionCache>()
|
||||||
|
|
||||||
private val librariesCache by cachedValue(project, project.createLibrariesModificationTracker()) { LibrariesCache() }
|
private val librariesCache by softCachedValue(project, project.createLibrariesModificationTracker()) { LibrariesCache() }
|
||||||
|
|
||||||
fun getSessionProvider(
|
fun getSessionProvider(
|
||||||
rootModule: KtSourceModule,
|
rootModule: KtSourceModule,
|
||||||
|
|||||||
-19
@@ -95,25 +95,6 @@ internal fun KtDeclaration.isNonAnonymousClassOrObject() =
|
|||||||
&& this !is KtEnumEntry
|
&& this !is KtEnumEntry
|
||||||
|
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
|
||||||
internal inline operator fun <T> CachedValue<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
|
|
||||||
|
|
||||||
internal inline fun <T> cachedValue(project: Project, vararg dependencies: Any, crossinline createValue: () -> T) =
|
|
||||||
CachedValuesManager.getManager(project).createCachedValue {
|
|
||||||
CachedValueProvider.Result(
|
|
||||||
createValue(),
|
|
||||||
dependencies
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a value which will be cached until until any physical PSI change happens
|
|
||||||
*
|
|
||||||
* @see com.intellij.psi.util.CachedValue
|
|
||||||
* @see com.intellij.psi.util.PsiModificationTracker.MODIFICATION_COUNT
|
|
||||||
*/
|
|
||||||
internal fun <T> psiModificationTrackerBasedCachedValue(project: Project, createValue: () -> T) =
|
|
||||||
cachedValue(project, PsiModificationTracker.MODIFICATION_COUNT, createValue = createValue)
|
|
||||||
|
|
||||||
fun KtElement.getElementTextInContext(): String {
|
fun KtElement.getElementTextInContext(): String {
|
||||||
val context = parentOfType<KtImportDirective>()
|
val context = parentOfType<KtImportDirective>()
|
||||||
|
|||||||
+2
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.asJava.KotlinAsJavaSupport
|
|||||||
import org.jetbrains.kotlin.asJava.finder.JavaElementFinder
|
import org.jetbrains.kotlin.asJava.finder.JavaElementFinder
|
||||||
import org.jetbrains.kotlin.fir.declarations.SealedClassInheritorsProvider
|
import org.jetbrains.kotlin.fir.declarations.SealedClassInheritorsProvider
|
||||||
import org.jetbrains.kotlin.light.classes.symbol.IDEKotlinAsJavaFirSupport
|
import org.jetbrains.kotlin.light.classes.symbol.IDEKotlinAsJavaFirSupport
|
||||||
|
import org.jetbrains.kotlin.light.classes.symbol.caches.SymbolLightClassFacadeCache
|
||||||
import org.jetbrains.kotlin.load.kotlin.PackagePartProvider
|
import org.jetbrains.kotlin.load.kotlin.PackagePartProvider
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.test.model.TestModule
|
import org.jetbrains.kotlin.test.model.TestModule
|
||||||
@@ -60,6 +61,7 @@ fun MockProject.registerTestServices(
|
|||||||
registerService(KotlinDeclarationProviderFactory::class.java, KotlinStaticDeclarationProviderFactory(allKtFiles))
|
registerService(KotlinDeclarationProviderFactory::class.java, KotlinStaticDeclarationProviderFactory(allKtFiles))
|
||||||
registerService(KotlinPackageProviderFactory::class.java, KotlinStaticPackageProviderFactory(allKtFiles))
|
registerService(KotlinPackageProviderFactory::class.java, KotlinStaticPackageProviderFactory(allKtFiles))
|
||||||
registerService(ProjectStructureProvider::class.java, KotlinProjectStructureProviderTestImpl(testServices))
|
registerService(ProjectStructureProvider::class.java, KotlinProjectStructureProviderTestImpl(testServices))
|
||||||
|
registerService(SymbolLightClassFacadeCache::class.java)
|
||||||
reRegisterJavaElementFinder(this)
|
reRegisterJavaElementFinder(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ dependencies {
|
|||||||
implementation(project(":compiler:light-classes"))
|
implementation(project(":compiler:light-classes"))
|
||||||
implementation(project(":analysis:analysis-api-providers"))
|
implementation(project(":analysis:analysis-api-providers"))
|
||||||
implementation(project(":analysis:analysis-api"))
|
implementation(project(":analysis:analysis-api"))
|
||||||
|
implementation(project(":analysis:analysis-internal-utils"))
|
||||||
implementation(project(":analysis:project-structure"))
|
implementation(project(":analysis:project-structure"))
|
||||||
implementation(intellijCoreDep()) { includeJars("intellij-core", rootProject = rootProject) }
|
implementation(intellijCoreDep()) { includeJars("intellij-core", rootProject = rootProject) }
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-3
@@ -15,8 +15,8 @@ import org.jetbrains.kotlin.asJava.KotlinAsJavaSupport
|
|||||||
import org.jetbrains.kotlin.asJava.classes.KtFakeLightClass
|
import org.jetbrains.kotlin.asJava.classes.KtFakeLightClass
|
||||||
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||||
import org.jetbrains.kotlin.fileClasses.javaFileFacadeFqName
|
import org.jetbrains.kotlin.fileClasses.javaFileFacadeFqName
|
||||||
|
import org.jetbrains.kotlin.light.classes.symbol.caches.SymbolLightClassFacadeCache
|
||||||
import org.jetbrains.kotlin.light.classes.symbol.classes.getOrCreateFirLightClass
|
import org.jetbrains.kotlin.light.classes.symbol.classes.getOrCreateFirLightClass
|
||||||
import org.jetbrains.kotlin.light.classes.symbol.classes.getOrCreateFirLightFacade
|
|
||||||
import org.jetbrains.kotlin.name.ClassId
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.name.parentOrNull
|
import org.jetbrains.kotlin.name.parentOrNull
|
||||||
@@ -90,7 +90,9 @@ class IDEKotlinAsJavaFirSupport(private val project: Project) : KotlinAsJavaSupp
|
|||||||
override fun getFacadeClasses(facadeFqName: FqName, scope: GlobalSearchScope): Collection<PsiClass> =
|
override fun getFacadeClasses(facadeFqName: FqName, scope: GlobalSearchScope): Collection<PsiClass> =
|
||||||
//TODO Split by modules
|
//TODO Split by modules
|
||||||
findFilesForFacade(facadeFqName, scope).ifNotEmpty {
|
findFilesForFacade(facadeFqName, scope).ifNotEmpty {
|
||||||
listOfNotNull(getOrCreateFirLightFacade(this.toList(), facadeFqName))
|
listOfNotNull(
|
||||||
|
project.getService(SymbolLightClassFacadeCache::class.java).getOrCreateSymbolLightFacade(this.toList(), facadeFqName)
|
||||||
|
)
|
||||||
} ?: emptyList()
|
} ?: emptyList()
|
||||||
|
|
||||||
override fun getScriptClasses(scriptFqName: FqName, scope: GlobalSearchScope): Collection<PsiClass> =
|
override fun getScriptClasses(scriptFqName: FqName, scope: GlobalSearchScope): Collection<PsiClass> =
|
||||||
@@ -105,7 +107,10 @@ class IDEKotlinAsJavaFirSupport(private val project: Project) : KotlinAsJavaSupp
|
|||||||
.asSequence()
|
.asSequence()
|
||||||
.filter { it.isFromSource() }
|
.filter { it.isFromSource() }
|
||||||
.groupBy { it.javaFileFacadeFqName }
|
.groupBy { it.javaFileFacadeFqName }
|
||||||
.mapNotNull { getOrCreateFirLightFacade(it.value, it.key) }
|
.mapNotNull {
|
||||||
|
project.getService(SymbolLightClassFacadeCache::class.java)
|
||||||
|
.getOrCreateSymbolLightFacade(it.value, it.key)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getFacadeNames(packageFqName: FqName, scope: GlobalSearchScope): Collection<String> =
|
override fun getFacadeNames(packageFqName: FqName, scope: GlobalSearchScope): Collection<String> =
|
||||||
project.createDeclarationProvider(scope)
|
project.createDeclarationProvider(scope)
|
||||||
|
|||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.light.classes.symbol.caches
|
||||||
|
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import org.jetbrains.kotlin.analysis.providers.createProjectWideOutOfBlockModificationTracker
|
||||||
|
import org.jetbrains.kotlin.light.classes.symbol.FirLightClassForFacade
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
import org.jetbrains.kotlin.analysis.utils.caches.*
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
class SymbolLightClassFacadeCache(project: Project) {
|
||||||
|
private val cache by softCachedValue(
|
||||||
|
project,
|
||||||
|
project.createProjectWideOutOfBlockModificationTracker()
|
||||||
|
) {
|
||||||
|
ConcurrentHashMap<FacadeKey, FirLightClassForFacade>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getOrCreateSymbolLightFacade(
|
||||||
|
ktFiles: List<KtFile>,
|
||||||
|
facadeClassFqName: FqName,
|
||||||
|
): FirLightClassForFacade? {
|
||||||
|
if (ktFiles.isEmpty()) return null
|
||||||
|
val key = FacadeKey(facadeClassFqName, ktFiles.toSet())
|
||||||
|
return cache.computeIfAbsent(key) {
|
||||||
|
getOrCreateFirLightFacadeNoCache(ktFiles, facadeClassFqName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getOrCreateFirLightFacadeNoCache(
|
||||||
|
ktFiles: List<KtFile>,
|
||||||
|
facadeClassFqName: FqName,
|
||||||
|
): FirLightClassForFacade {
|
||||||
|
val firstFile = ktFiles.first()
|
||||||
|
return FirLightClassForFacade(firstFile.manager, facadeClassFqName, ktFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class FacadeKey(val fqName: FqName, val files: Set<KtFile>)
|
||||||
|
}
|
||||||
+1
-21
@@ -92,28 +92,8 @@ internal fun KtClassOrObjectSymbol.createLightClassNoCache(manager: PsiManager):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOrCreateFirLightFacade(
|
|
||||||
ktFiles: List<KtFile>,
|
|
||||||
facadeClassFqName: FqName,
|
|
||||||
): FirLightClassForFacade? {
|
|
||||||
val firstFile = ktFiles.firstOrNull() ?: return null
|
|
||||||
//TODO Make caching keyed by all files
|
|
||||||
return CachedValuesManager.getCachedValue(firstFile) {
|
|
||||||
CachedValueProvider.Result
|
|
||||||
.create(
|
|
||||||
getOrCreateFirLightFacadeNoCache(ktFiles, facadeClassFqName),
|
|
||||||
firstFile.project.createProjectWideOutOfBlockModificationTracker()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getOrCreateFirLightFacadeNoCache(
|
|
||||||
ktFiles: List<KtFile>,
|
|
||||||
facadeClassFqName: FqName,
|
|
||||||
): FirLightClassForFacade? {
|
|
||||||
val firstFile = ktFiles.firstOrNull() ?: return null
|
|
||||||
return FirLightClassForFacade(firstFile.manager, facadeClassFqName, ktFiles)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun lightClassForEnumEntry(ktEnumEntry: KtEnumEntry): KtLightClass? {
|
private fun lightClassForEnumEntry(ktEnumEntry: KtEnumEntry): KtLightClass? {
|
||||||
|
|||||||
@@ -2,4 +2,10 @@ plugins {
|
|||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
}
|
}
|
||||||
|
|
||||||
publishJarsForIde(listOf(":analysis:analysis-api-impl-barebone", ":analysis:analysis-api-impl-base"))
|
publishJarsForIde(
|
||||||
|
listOf(
|
||||||
|
":analysis:analysis-api-impl-barebone",
|
||||||
|
":analysis:analysis-api-impl-base",
|
||||||
|
":analysis:analysis-internal-utils"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|||||||
@@ -482,6 +482,7 @@ include ":generators:analysis-api-generator",
|
|||||||
":analysis:analysis-api-impl-barebone",
|
":analysis:analysis-api-impl-barebone",
|
||||||
":analysis:analysis-api-impl-base",
|
":analysis:analysis-api-impl-base",
|
||||||
":analysis:analysis-api-providers",
|
":analysis:analysis-api-providers",
|
||||||
|
":analysis:analysis-internal-utils",
|
||||||
":analysis:symbol-light-classes",
|
":analysis:symbol-light-classes",
|
||||||
":analysis:project-structure",
|
":analysis:project-structure",
|
||||||
":analysis:analysis-api-fe10"
|
":analysis:analysis-api-fe10"
|
||||||
|
|||||||
Reference in New Issue
Block a user