Skip to content

Commit

Permalink
improvement: Add error reports upon compiler access error (#5315)
Browse files Browse the repository at this point in the history
  • Loading branch information
kasiaMarek authored Jun 27, 2023
1 parent 6e9bca7 commit 8a029f3
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ import java.util.logging.Logger
import scala.concurrent.ExecutionContextExecutor
import scala.util.control.NonFatal

import scala.meta.internal.metals.Report
import scala.meta.internal.metals.ReportContext
import scala.meta.pc.CancelToken
import scala.meta.pc.OffsetParams
import scala.meta.pc.PresentationCompilerConfig
import scala.meta.pc.VirtualFileParams

/**
* Manages the lifecycle and multi-threaded access to the presentation compiler.
Expand All @@ -25,7 +29,7 @@ abstract class CompilerAccess[Reporter, Compiler](
sh: Option[ScheduledExecutorService],
newCompiler: () => CompilerWrapper[Reporter, Compiler],
shouldResetJobQueue: Boolean
)(implicit ec: ExecutionContextExecutor) {
)(implicit ec: ExecutionContextExecutor, rc: ReportContext) {
private val logger: Logger =
Logger.getLogger(classOf[CompilerAccess[_, _]].getName)

Expand Down Expand Up @@ -76,7 +80,7 @@ abstract class CompilerAccess[Reporter, Compiler](
/**
* Asynchronously execute a function on the compiler thread with `Thread.interrupt()` cancellation.
*/
def withInterruptableCompiler[T](
def withInterruptableCompiler[T](params: Option[VirtualFileParams])(
default: T,
token: CancelToken
)(thunk: CompilerWrapper[Reporter, Compiler] => T): CompletableFuture[T] = {
Expand All @@ -85,7 +89,7 @@ abstract class CompilerAccess[Reporter, Compiler](
val result = onCompilerJobQueue(
() => {
queueThread = Some(Thread.currentThread())
try withSharedCompiler(default)(thunk)
try withSharedCompiler(params)(default)(thunk)
finally isFinished.set(true)
},
token
Expand Down Expand Up @@ -119,18 +123,23 @@ abstract class CompilerAccess[Reporter, Compiler](
* Note that the function is still cancellable.
*/
def withNonInterruptableCompiler[T](
params: Option[VirtualFileParams]
)(
default: T,
token: CancelToken
)(thunk: CompilerWrapper[Reporter, Compiler] => T): CompletableFuture[T] = {
onCompilerJobQueue(() => withSharedCompiler(default)(thunk), token)
onCompilerJobQueue(
() => withSharedCompiler(params)(default)(thunk),
token
)
}

/**
* Execute a function on the current thread without cancellation support.
*
* May potentially run in parallel with other requests, use carefully.
*/
def withSharedCompiler[T](
def withSharedCompiler[T](params: Option[VirtualFileParams])(
default: T
)(thunk: CompilerWrapper[Reporter, Compiler] => T): T = {
try {
Expand All @@ -141,14 +150,14 @@ abstract class CompilerAccess[Reporter, Compiler](
case other: Throwable =>
handleSharedCompilerException(other)
.map { message =>
retryWithCleanCompiler(
retryWithCleanCompiler(params)(
thunk,
default,
message
)
}
.getOrElse {
handleError(other)
handleError(other, params)
default
}
}
Expand All @@ -159,6 +168,8 @@ abstract class CompilerAccess[Reporter, Compiler](
protected def ignoreException(t: Throwable): Boolean

private def retryWithCleanCompiler[T](
params: Option[VirtualFileParams]
)(
thunk: CompilerWrapper[Reporter, Compiler] => T,
default: T,
cause: String
Expand All @@ -173,14 +184,57 @@ abstract class CompilerAccess[Reporter, Compiler](
case InterruptException() =>
default
case NonFatal(e) =>
handleError(e)
handleError(e, params)
default
}
}

private def handleError(e: Throwable): Unit = {
CompilerThrowable.trimStackTrace(e)
logger.log(Level.SEVERE, e.getMessage, e)
private def handleError(
e: Throwable,
params: Option[VirtualFileParams]
): Unit = {
def virtualFileParamsToString(v: VirtualFileParams) =
s"""|file uri: ${v.uri()}
|file text:
|${v.text()}
|""".stripMargin
val stacktrace = Thread.currentThread().getStackTrace().mkString("\n")
val error = CompilerThrowable.trimStackTrace(e)
val paramsText =
params match {
case None => ""
case Some(r: RangeOffset) =>
s"""|range ${r.start} - ${r.end}
|${virtualFileParamsToString(r)}
|""".stripMargin
case Some(o: OffsetParams) =>
s"""|offset ${o.offset()}
|${virtualFileParamsToString(o)}
|""".stripMargin
case Some(v) => virtualFileParamsToString(v)
}
val pathToFull =
rc.unsanitized.create(
Report(
"compiler-error",
s"""|An error occurred in the presentation compiler:
|context stacktrace:
|${stacktrace}
|action params:
|$paramsText
|""".stripMargin,
error = Some(error)
)
)
pathToFull match {
case Some(path) =>
logger.log(
Level.SEVERE,
s"A severe compiler error occurred, full details of the error can be found in the error report $path"
)
case _ =>
logger.log(Level.SEVERE, e.getMessage, e)
}
shutdownCurrentCompiler()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import scala.tools.nsc.interactive.ShutdownReq
import scala.tools.nsc.reporters.StoreReporter
import scala.util.control.NonFatal

import scala.meta.internal.metals.ReportContext
import scala.meta.pc.PresentationCompilerConfig

class ScalaCompilerWrapper(global: MetalsGlobal)
Expand Down Expand Up @@ -43,7 +44,7 @@ class ScalaCompilerAccess(
config: PresentationCompilerConfig,
sh: Option[ScheduledExecutorService],
newCompiler: () => ScalaCompilerWrapper
)(implicit ec: ExecutionContextExecutor)
)(implicit ec: ExecutionContextExecutor, rc: ReportContext)
extends CompilerAccess[StoreReporter, MetalsGlobal](
config,
sh,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import scala.tools.nsc.Settings
import scala.tools.nsc.reporters.StoreReporter

import scala.meta.internal.jdk.CollectionConverters._
import scala.meta.internal.metals.CompilerVirtualFileParams
import scala.meta.internal.metals.EmptyCancelToken
import scala.meta.internal.metals.EmptyReportContext
import scala.meta.internal.metals.ReportContext
Expand Down Expand Up @@ -103,7 +104,8 @@ case class ScalaPresentationCompiler(
sh,
() => new ScalaCompilerWrapper(newCompiler())
)(
ec
ec,
reportContex
)

override def shutdown(): Unit = {
Expand Down Expand Up @@ -139,9 +141,8 @@ case class ScalaPresentationCompiler(
override def semanticTokens(
params: VirtualFileParams
): CompletableFuture[ju.List[Node]] = {

val empty: ju.List[Node] = new ju.ArrayList[Node]()
compilerAccess.withInterruptableCompiler(
compilerAccess.withInterruptableCompiler(Some(params))(
empty,
params.token
) { pc =>
Expand All @@ -155,7 +156,7 @@ case class ScalaPresentationCompiler(
override def complete(
params: OffsetParams
): CompletableFuture[CompletionList] =
compilerAccess.withInterruptableCompiler(
compilerAccess.withInterruptableCompiler(Some(params))(
EmptyCompletionList(),
params.token
) { pc =>
Expand All @@ -166,7 +167,10 @@ case class ScalaPresentationCompiler(
params: OffsetParams
): CompletableFuture[ju.List[TextEdit]] = {
val empty: ju.List[TextEdit] = new ju.ArrayList[TextEdit]()
compilerAccess.withInterruptableCompiler(empty, params.token) { pc =>
compilerAccess.withInterruptableCompiler(Some(params))(
empty,
params.token
) { pc =>
new CompletionProvider(pc.compiler(), params).implementAll()
}
}
Expand All @@ -175,7 +179,10 @@ case class ScalaPresentationCompiler(
params: OffsetParams
): CompletableFuture[ju.List[TextEdit]] = {
val empty: ju.List[TextEdit] = new ju.ArrayList[TextEdit]()
compilerAccess.withInterruptableCompiler(empty, params.token) { pc =>
compilerAccess.withInterruptableCompiler(Some(params))(
empty,
params.token
) { pc =>
new InferredTypeProvider(pc.compiler(), params).inferredTypeEdits().asJava
}
}
Expand All @@ -185,7 +192,7 @@ case class ScalaPresentationCompiler(
): CompletableFuture[ju.List[TextEdit]] = {
val empty: Either[String, List[TextEdit]] = Right(List())
(compilerAccess
.withInterruptableCompiler(empty, params.token) { pc =>
.withInterruptableCompiler(Some(params))(empty, params.token) { pc =>
new PcInlineValueProviderImpl(pc.compiler(), params).getInlineTextEdits
})
.thenApply {
Expand All @@ -199,12 +206,13 @@ case class ScalaPresentationCompiler(
extractionPos: OffsetParams
): CompletableFuture[ju.List[TextEdit]] = {
val empty: ju.List[TextEdit] = new ju.ArrayList[TextEdit]()
compilerAccess.withInterruptableCompiler(empty, range.token) { pc =>
new ExtractMethodProvider(
pc.compiler(),
range,
extractionPos
).extractMethod.asJava
compilerAccess.withInterruptableCompiler(Some(range))(empty, range.token) {
pc =>
new ExtractMethodProvider(
pc.compiler(),
range,
extractionPos
).extractMethod.asJava
}
}

Expand All @@ -214,7 +222,7 @@ case class ScalaPresentationCompiler(
): CompletableFuture[ju.List[TextEdit]] = {
val empty: Either[String, List[TextEdit]] = Right(List())
(compilerAccess
.withInterruptableCompiler(empty, params.token) { pc =>
.withInterruptableCompiler(Some(params))(empty, params.token) { pc =>
new ConvertToNamedArgumentsProvider(
pc.compiler(),
params,
Expand All @@ -232,7 +240,7 @@ case class ScalaPresentationCompiler(
params: OffsetParams,
isExtension: java.lang.Boolean // ignore, because Scala2 doesn't support extension method
): CompletableFuture[ju.List[AutoImportsResult]] =
compilerAccess.withInterruptableCompiler(
compilerAccess.withInterruptableCompiler(Some(params))(
List.empty[AutoImportsResult].asJava,
params.token
) { pc =>
Expand All @@ -254,23 +262,23 @@ case class ScalaPresentationCompiler(
symbol: String
): CompletableFuture[CompletionItem] =
CompletableFuture.completedFuture {
compilerAccess.withSharedCompiler(item) { pc =>
compilerAccess.withSharedCompiler(None)(item) { pc =>
new CompletionItemResolver(pc.compiler()).resolve(item, symbol)
}
}

override def signatureHelp(
params: OffsetParams
): CompletableFuture[SignatureHelp] =
compilerAccess.withNonInterruptableCompiler(
compilerAccess.withNonInterruptableCompiler(Some(params))(
new SignatureHelp(),
params.token
) { pc => new SignatureHelpProvider(pc.compiler()).signatureHelp(params) }

override def prepareRename(
params: OffsetParams
): CompletableFuture[ju.Optional[Range]] =
compilerAccess.withNonInterruptableCompiler(
compilerAccess.withNonInterruptableCompiler(Some(params))(
Optional.empty[Range](),
params.token
) { pc =>
Expand All @@ -281,7 +289,7 @@ case class ScalaPresentationCompiler(
params: OffsetParams,
name: String
): CompletableFuture[ju.List[TextEdit]] =
compilerAccess.withNonInterruptableCompiler(
compilerAccess.withNonInterruptableCompiler(Some(params))(
List[TextEdit]().asJava,
params.token
) { pc =>
Expand All @@ -291,7 +299,7 @@ case class ScalaPresentationCompiler(
override def hover(
params: OffsetParams
): CompletableFuture[Optional[HoverSignature]] =
compilerAccess.withNonInterruptableCompiler(
compilerAccess.withNonInterruptableCompiler(Some(params))(
Optional.empty[HoverSignature](),
params.token
) { pc =>
Expand All @@ -301,7 +309,7 @@ case class ScalaPresentationCompiler(
}

def definition(params: OffsetParams): CompletableFuture[DefinitionResult] = {
compilerAccess.withNonInterruptableCompiler(
compilerAccess.withNonInterruptableCompiler(Some(params))(
DefinitionResultImpl.empty,
params.token
) { pc => new PcDefinitionProvider(pc.compiler(), params).definition() }
Expand All @@ -310,7 +318,7 @@ case class ScalaPresentationCompiler(
override def typeDefinition(
params: OffsetParams
): CompletableFuture[DefinitionResult] = {
compilerAccess.withNonInterruptableCompiler(
compilerAccess.withNonInterruptableCompiler(Some(params))(
DefinitionResultImpl.empty,
params.token
) { pc => new PcDefinitionProvider(pc.compiler(), params).typeDefinition() }
Expand All @@ -319,26 +327,27 @@ case class ScalaPresentationCompiler(
override def documentHighlight(
params: OffsetParams
): CompletableFuture[util.List[DocumentHighlight]] =
compilerAccess.withInterruptableCompiler(
compilerAccess.withInterruptableCompiler(Some(params))(
List.empty[DocumentHighlight].asJava,
params.token()
) { pc =>
new PcDocumentHighlightProvider(pc.compiler(), params).highlights().asJava
}

override def semanticdbTextDocument(
uri: URI,
fileUri: URI,
code: String
): CompletableFuture[Array[Byte]] = {
compilerAccess.withInterruptableCompiler(
val virtualFile = CompilerVirtualFileParams(fileUri, code)
compilerAccess.withInterruptableCompiler(Some(virtualFile))(
Array.emptyByteArray,
EmptyCancelToken
) { pc =>
new SemanticdbTextDocumentProvider(
pc.compiler(),
config.semanticdbCompilerOptions().asScala.toList
)
.textDocument(uri, code)
.textDocument(fileUri, code)
.toByteArray
}
}
Expand All @@ -347,11 +356,12 @@ case class ScalaPresentationCompiler(
params: ju.List[OffsetParams]
): CompletableFuture[ju.List[SelectionRange]] = {
CompletableFuture.completedFuture {
compilerAccess.withSharedCompiler(List.empty[SelectionRange].asJava) {
pc =>
new SelectionRangeProvider(pc.compiler(), params)
.selectionRange()
.asJava
compilerAccess.withSharedCompiler(params.asScala.headOption)(
List.empty[SelectionRange].asJava
) { pc =>
new SelectionRangeProvider(pc.compiler(), params)
.selectionRange()
.asJava
}
}
}
Expand Down
Loading

0 comments on commit 8a029f3

Please sign in to comment.