-
-
Notifications
You must be signed in to change notification settings - Fork 360
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add example docs for classloader workers (#3796)
Fixes #3794 It's still pretty messy, but i'm just documenting the existing APIs that already exist, and at least it provides a paved path for people to get _something_ working. Cleaning them up can come separately in #3775
- Loading branch information
Showing
11 changed files
with
148 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
def htmlContent = "<html><body><h1>Hello!</h1><p>" + args[0] + "</p></body></html>" | ||
|
||
def outputFile = new File(args[1]) | ||
outputFile.write(htmlContent) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package bar; | ||
|
||
import java.io.IOException; | ||
import java.io.InputStream; | ||
|
||
public class Bar { | ||
|
||
// Read `file.txt` from classpath | ||
public static String groovyGeneratedHtml() throws IOException { | ||
// Get the resource as an InputStream | ||
try (InputStream inputStream = Bar.class.getClassLoader().getResourceAsStream("groovy-generated.html")) { | ||
return new String(inputStream.readAllBytes()); | ||
} | ||
} | ||
|
||
public static void main(String[] args) throws IOException{ | ||
String appClasspathResourceText = Bar.groovyGeneratedHtml(); | ||
System.out.println("Contents of groovy-generated.html is " + appClasspathResourceText); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// Althought running JVM bytecode via a one-off isolated classloader has less overhead | ||
// than running it in a subprocess, the fact that the classloader needs to be created | ||
// each time adds overhead: newly-created classloaders contain code that is not yet | ||
// optimized by the JVM. When performance matters, you can put the classloader in a | ||
// `Task.Worker` to keep it around, allowing the code internally to be optimized and | ||
// stay optimized without being thrown away each time | ||
|
||
// This example is similar to the earlier example running the Groovy interpreter in | ||
// a subprocess, but instead of using `Jvm.runSubprocess` we use `Jvm.inprocess` to | ||
// load the Groovy interpreter classpath files into an in-memory in-process classloader: | ||
|
||
package build | ||
import mill._, javalib._ | ||
import mill.util.Jvm | ||
|
||
object coursierModule extends CoursierModule | ||
|
||
def groovyClasspath: Task[Agg[PathRef]] = Task{ | ||
coursierModule.defaultResolver().resolveDeps(Agg(ivy"org.codehaus.groovy:groovy:3.0.9")) | ||
} | ||
|
||
def groovyWorker: Worker[java.net.URLClassLoader] = Task.Worker{ | ||
mill.api.ClassLoader.create(groovyClasspath().map(_.path.toIO.toURL).toSeq, parent = null) | ||
} | ||
|
||
trait GroovyGenerateJavaModule extends JavaModule { | ||
def groovyScript = Task.Source(millSourcePath / "generate.groovy") | ||
|
||
def groovyGeneratedResources = Task{ | ||
val oldCl = Thread.currentThread().getContextClassLoader | ||
Thread.currentThread().setContextClassLoader(groovyWorker()) | ||
try { | ||
groovyWorker() | ||
.loadClass("groovy.ui.GroovyMain") | ||
.getMethod("main", classOf[Array[String]]) | ||
.invoke( | ||
null, | ||
Array[String]( | ||
groovyScript().path.toString, | ||
groovyGenerateArg(), | ||
(Task.dest / "groovy-generated.html").toString | ||
) | ||
) | ||
} finally Thread.currentThread().setContextClassLoader(oldCl) | ||
PathRef(Task.dest) | ||
} | ||
|
||
def groovyGenerateArg: T[String] | ||
def resources = super.resources() ++ Seq(groovyGeneratedResources()) | ||
} | ||
|
||
object foo extends GroovyGenerateJavaModule{ | ||
def groovyGenerateArg = "Foo Groovy!" | ||
} | ||
object bar extends GroovyGenerateJavaModule{ | ||
def groovyGenerateArg = "Bar Groovy!" | ||
} | ||
|
||
// Here we have two modules `foo` and `bar`, each of which makes use of `groovyWorker` | ||
// to evaluate a groovy script to generate some resources. In this case, we invoke the `main` | ||
// method of `groovy.ui.GroovyMain`, which also happens to require us to set the | ||
// `ContextClassLoader` to work. | ||
|
||
|
||
/** Usage | ||
|
||
> ./mill foo.run | ||
Contents of groovy-generated.html is <html><body><h1>Hello!</h1><p>Foo Groovy!</p></body></html> | ||
|
||
> ./mill bar.run | ||
Contents of groovy-generated.html is <html><body><h1>Hello!</h1><p>Bar Groovy!</p></body></html> | ||
*/ | ||
|
||
|
||
// Because the `URLClassLoader` within `groovyWorker` is long-lived, the code within the | ||
// classloader can be optimized by the JVM runtime, and would have less overhead than if | ||
// run in separate classloaders via `Jvm.runClassloader`. And because `URLClassLoader` | ||
// already extends `AutoCloseable`, `groovyWorker` gets treated as an | ||
// xref:fundamentals/tasks.adoc#_autoclosable_workers[Autocloseable Worker] automatically. | ||
|
||
// NOTE: As mentioned in documentation for xref:fundamentals/tasks.adoc#_workers[Worker Tasks], | ||
// the classloader contained within `groovyWorker` above is *initialized* in a single-thread, | ||
// but it may be *used* concurrently in a multi-threaded environment. Practically, that means | ||
// that the classes and methods you are invoking within the classloader do not make use of | ||
// un-synchronized global mutable variables. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
def htmlContent = "<html><body><h1>Hello!</h1><p>" + args[0] + "</p></body></html>" | ||
|
||
def outputFile = new File(args[1]) | ||
outputFile.write(htmlContent) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package foo; | ||
|
||
import java.io.IOException; | ||
import java.io.InputStream; | ||
|
||
public class Foo { | ||
|
||
// Read `file.txt` from classpath | ||
public static String groovyGeneratedHtml() throws IOException { | ||
// Get the resource as an InputStream | ||
try (InputStream inputStream = Foo.class.getClassLoader().getResourceAsStream("groovy-generated.html")) { | ||
return new String(inputStream.readAllBytes()); | ||
} | ||
} | ||
|
||
public static void main(String[] args) throws IOException{ | ||
String appClasspathResourceText = Foo.groovyGeneratedHtml(); | ||
System.out.println("Contents of groovy-generated.html is " + appClasspathResourceText); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters