Skip to content

Commit

Permalink
Merge pull request #581 from iusildra/check-no-reload
Browse files Browse the repository at this point in the history
Test no reload when no changes & refresh breakpoints
  • Loading branch information
adpi2 authored Sep 20, 2023
2 parents 0353914 + 04c4237 commit 3dc1c9f
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import scala.util.Failure
import scala.util.Success
import scala.util.Try
import scala.util.control.NonFatal
import io.reactivex.subjects.PublishSubject

class HotCodeReplaceProvider(
debuggee: Debuggee,
Expand All @@ -39,13 +40,15 @@ class HotCodeReplaceProvider(
private var currentDebugSession: IDebugSession = null
private val classesAccumulator: AtomicReference[Set[String]] = new AtomicReference(Set.empty)
private val threadFrameMap: mutable.Map[ThreadReference, Seq[StackFrame]] = mutable.Map.empty
private val eventSubject = PublishSubject.create[HotCodeReplaceEvent]()

override def initialize(context: IDebugAdapterContext, options: ju.Map[String, Object]): Unit = {
this.context = context
this.currentDebugSession = context.getDebugSession
this.subscription = debuggee.classesToUpdate.subscribe(classes => classesAccumulator.updateAndGet(_ ++ classes))
this.sourceLookUp = context.getProvider(classOf[ISourceLookUpProvider]).asInstanceOf[SourceLookUpProvider]
this.stackTraceProvider = context.getProvider(classOf[IStackTraceProvider]).asInstanceOf[StackTraceProvider]

}

override def close(): Unit = {
Expand Down Expand Up @@ -88,27 +91,44 @@ class HotCodeReplaceProvider(
Failure(new DebugException("JVM does not support popping frames"))
else Success(())

override def getEventHub: Observable[HotCodeReplaceEvent] =
Observable.empty()
override val getEventHub: Observable[HotCodeReplaceEvent] = eventSubject

private def publishEvent(tpe: HotCodeReplaceEvent.EventType, message: String): Unit = {
eventSubject.onNext(new HotCodeReplaceEvent(tpe, message));
}

private def publishEvent(tpe: HotCodeReplaceEvent.EventType, message: String, data: AnyRef): Unit = {
eventSubject.onNext(new HotCodeReplaceEvent(tpe, message, data));
}

private def doHotCodeReplace(classesToReplace: Seq[String]): Try[Unit] = {
val res = for {
suspendedThreads <- getSuspendedThreads()
poppedThreads <- canPopFrames.transform(
_ => attemptPopFrames(suspendedThreads, classesToReplace),
e => Try(warnOrThrow("Cannot pop frames")).map(_ => Seq.empty)
)
_ = redefineClasses(classesToReplace)
res <-
if (containsObsoleteMethods(suspendedThreads))
Failure(new DebugException("Failed to complete hot code replace: JVM still contains obsolete code"))
else {
poppedThreads.foreach(stepIntoThread)
Success(())
}
} yield res
threadFrameMap.clear()
res
if (!currentDebugSession.getVM().canRedefineClasses()) {
val err = "JVM doesn't support hot reload classes"
publishEvent(HotCodeReplaceEvent.EventType.ERROR, err)
Failure(new DebugException(err))
} else {
val res = for {
suspendedThreads <- getSuspendedThreads()
poppedThreads <- canPopFrames.transform(
_ => attemptPopFrames(suspendedThreads, classesToReplace),
e => Try(warnOrThrow("Cannot pop frames")).map(_ => Seq.empty)
)
_ = publishEvent(HotCodeReplaceEvent.EventType.STARTING, "Start hot code replacement procedure...")
_ = redefineClasses(classesToReplace)
res <-
if (containsObsoleteMethods(suspendedThreads)) {
val err = "JVM contains obsolete methods"
publishEvent(HotCodeReplaceEvent.EventType.ERROR, err);
Failure(new DebugException(s"Failed to complete hot code replace: $err"))
} else {
poppedThreads.foreach(stepIntoThread)
Success(())
}
} yield res
publishEvent(HotCodeReplaceEvent.EventType.END, "Completed hot code replace", classesToReplace.asJava)
threadFrameMap.clear()
res
}
}

private def warnOrThrow(message: String) =
Expand Down Expand Up @@ -209,7 +229,7 @@ class HotCodeReplaceProvider(
_: UnsupportedOperationException | _: NoClassDefFoundError | _: VerifyError | _: ClassFormatError |
_: ClassCircularityError
) =>
// publishEvent(HotCodeReplaceEvent.EventType.ERROR, e.getMessage());
publishEvent(HotCodeReplaceEvent.EventType.ERROR, e.getMessage());
Failure(new DebugException("Failed to redefine classes: " + e.getMessage()))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ def checkHotCodeReplaceTask = Def.task {
DebugTest.runChecks(DebugState.state)(Seq(RedefineClasses(), Outputed("C")))
}

def checkNoHotCodeReplaceTask = Def.task {
val _ = (Compile / compile).value
DebugTest.runChecks(DebugState.state)(Seq(Outputed("B")))
}

lazy val a: Project =
project
.in(file("."))
Expand All @@ -40,3 +45,14 @@ lazy val b =
sourceToDebug := (a / sourceToDebug).value
)
.dependsOn(a)

lazy val c =
project
.in(file("c"))
.settings(
scalaVersion := scalaV,
checkBreakpoint := checkBreakpointTask.evaluated,
checkHotCodeReplace := checkNoHotCodeReplaceTask.value,
sourceToDebug := (a / sourceToDebug).value
)
.dependsOn(a)
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ $ copy-file ./src/main/scala/example/A.scala.2 ./src/main/scala/example/A.scala
> b / checkHotCodeReplace
> b / stopDebugSession

$ copy-file ./src/main/scala/example/A.scala.1 ./src/main/scala/example/A.scala
> 'c / checkBreakpoint {"class":"example.Main","arguments":[],"jvmOptions":[]}'
> c / checkHotCodeReplace
> c / stopDebugSession

0 comments on commit 3dc1c9f

Please sign in to comment.