The Rationale Behind the Trampoline

I know what you're thinking, wise-hearted reader. "Why would anyone
need to provide a rationale for a trampoline? That's like trying to
give a reason for rainbows, or a newborn's laughter, or Michael
Jackson's Thriller."

Allow me to explain myself. lein trampoline does indeed give you a
feeling of weightless freedom, just not in the way that you're used
to.

See, whenever you use Leiningen to run code from your project, you end
up with two Java processes running. Each process loads a separate
instance of the
JVM. We saw this
in
the previous article
in the output of ps | grep lein. The first process is for Leiningen
itself, and it's responsible for setting up everything necessary for
your project code to run. The second process is where your code
actually executes. If you were to run lein -h, you would only start
one Java process, as none of your project code would need to be
executed.

Leiningen starts a separate process for your project in order to
enforce isolation. This is because Leiningen is a true gentleman who
does not allow his namespaces and dependencies (like, say, a
completely different version of Clojure) to interfere with your
meticulously hand-crafted, artisinal program.

However, like a doting father, the Leiningen process continues to stay
open for the entire duration of your program's execution. If you have
a long-running process, like a web server for your Justin Bieber fan
site, then Leiningen stays open the whole time consuming memory that
could be put to use in compositing images of the Biebs with hearts all
over them.

This is where the trampoline comes into play. It allows the Leiningen
process to exit completely before your project's process starts. Now,
instead of two JVM's running, you only have one.

I think the name "trampoline" was chosen to evoke an image of
Leiningen providing a launching point for your program. However, I
choose to envision the trampoline process as follows:

Leiningen takes your program, which is embodied as one of those
cymbal-clanging monkeys with unsettling eyes, and winds it up.

Leiningen gingerly places the wind-up monkey which is your program
on the floor of a cozy Hobbit hole.

Leiningen takes two warm-up bounces and then, with a mighty
"Hyup!", rockets himself into the sky, his mustaches flapping in
the wind. He grows smaller and smaller until, with a bright
sparkle, he disappears entirely.

How lein trampoline Works

Though you don't really need to understand how lein trampoline works
in order to use it, I think it's pretty cool. Below I walk you through
it step by step, with relevant code. We'll be using the project under
leiningen/lein-build of the
make-a-clojure-baby github repo.

Run lein trampoline run from the command line. If you're on a linux machine,
this executes a
bash script.
This script is probably at ~/bin/lein on your system.

The bash script
sets the TRAMPOLINE_FILE
environment variable to a path. Later in this process, Leiningen
will write a command to this file. Here's the part of the script
that sets the environment variable:

(defn ^:higher-ordertrampoline"Run a task without nesting the project's JVM inside Leiningen's.Calculates the Clojure code to run in the project's process for thegiven task and allows Leiningen's own JVM process to exit beforerunning it rather than launching a subprocess of Leiningen's JVM.Use this to save memory or to work around stdin issues."[project task-name&args](when (= :leiningen(:eval-inproject))(main/info"Warning: trampoline has no effect with :eval-in-leiningen."))(binding [*trampoline?*true](main/apply-task(main/lookup-aliastask-nameproject)(-> (assoc project :eval-in:trampoline)(vary-metaupdate-in[:without-profiles]assoc:eval-in:trampoline))args))(if (seq @eval/trampoline-forms)(write-trampolineproject @eval/trampoline-forms@eval/trampoline-profiles)(main/aborttask-name"did not run any project code for trampolining.")))

trampoline calls leiningen.core.main/apply-task but with a
twist: it passes that function an updated project configuration,
setting :eval-in to :trampoline. You can see this is the
snippet above.

Eventually, leiningen.core.eval/eval-in-project gets applied. The
cool thing about this function is that it then calls
leiningen.core.eval/eval-in, which is a multi-method. It has
different definitions for :subprocess, :trampoline, :nrepl,
and a few more. This is one of the first times I've seen
defmethod "in the wild" and it really tickled my pfeffernuesse.
Definitely
check it out on github.

Since we updated our project configuration in the last step so that
:eval-in is :trampoline, the :trampoline method gets matched:

This updates the trampoline-forms and trampoline-profiles atoms
within the leiningen.core.eval namespace.

The trampoline function shown in step 5 above continues
executing:

(if (seq @eval/trampoline-forms)(write-trampolineproject @eval/trampoline-forms@eval/trampoline-profiles)(main/aborttask-name"did not run any project code for trampolining.")))

write-trampoline writes out the entire Java command necessary to
finally run our project's main function. It writes this command
to the path in the TRAMPOLINE_FILE environment variable set by
the bash script in step 2 above.

The Leiningen process exits and the bash process from step 2
continues. It checks for the existence of TRAMPOLINE_FILE, and
since it exists, it essentially evaluates the command in it,
kicking off the Java process which will run your code: