It is not safe, as you yourself pointed out correctly that you'll have
leftover state between executions, or even worse, you will have
multiple requests working concurrently in the same scope, overwriting
each other's data.
You can however do this on each request:
ScriptableObject requestScope = Context.newObject(topScope);
requestScope.setPrototype(topScope);
this way, you will have an empty scope for every request, and all
functions will be looked up in its prototype (since they usually won't
be found in the request scope). The prototype is created once, and
shared across all request instances (hence the approach is usually
named "shared prototype").
There are caveats with this approach though too. Specifically, the
lexical scope for your functions will remain topScope (except with
dyamic scopes, more on that later), so they will not see variables in
the request scope, except when they're qualified with "this". I.e. if
you have a variable "a" in requestScope, your functions must be
written with "this.a" instead of "a" reference. Similarly, if function
f calls function g, instead of "g()" you'll have to write "this.g()",
otherwise "a" will not be visible (not even as "this.a") inside g.
You can get around this by using Rhino's dynamic scopes functionality.
It will replace lexical scoping (based on nesting of code in the
source code) with dynamic scoping (based on nesting of function
invocations in the actual execution). Then you won't have to use
"this." in your functions everywhere.
For different reasons though, I personally discourage use of dynamic
scopes. They make program execution harder to comprehend, and they're
also completely nonstandard - JS normally uses lexical scoping. Many
JS libraries out there will rely on scoping to achieve things like
simulating private functions etc. Using dynamic scopes can break them.
This mightn't matter much if all your code is homegrown, but if you
want to reuse some existing libraries, or hope that your homegrown
code might need to run in some other JS runtime in the future, it is a
concern.
And now for something completely different.
You can often get good performance even without resorting to any of
the above. Compile your script that defines functions and initializes
objects ("init script"). Then just create a new top level scope, run
initStandardObjects(), and run your init script in it, then proceed
with the request specific script. The init script is already compiled;
by executing it again you'll just have it create Function object
instances for top-level functions, bind them to their precompiled code
(it's available to the Script object they're in), and have it assigned
to variable names. There's nothing too costly involved in this step --
compilation already happened when you invoked Context.compileScript.
If you have nested functions, they don't even contribute to the cost;
a similar process will happen for them when the function containing
them is invoked.
Granted, it might be more intensive than the above two liner for
creating a requestScope and setting its prototype. But it is also
simpler; all your executing requests have a vanilla setup with a
single scope, they share nothing (well, except the immutable code of
the functions).
I've implemented Rhino-based JS runtimes using all of these
approaches: shared prototype with dynamic scopes, shared prototype
with static scopes + "this." everywhere in functions, and the vanilla
solution. The vanilla solution is surprisingly well performant and
more than sufficient in most cases.
Later on, if you find your system slow, and you can prove by profiling
that the bottleneck is in repeated initStandardObjects() + your init
script execution, you can step up to the shared prototype approach.
But I encourage you to try the vanilla approach first, as it results
in simplest, most easily maintained architecture.
Hope that helps.
Attila.
On 2008.05.18., at 5:00, James Burke wrote:
> Using Rhino with servlets: I want to do as much initialization and
> script compiling as possible when using Rhino in a servlet
> environment, to get the best performance. I am using Rhino 1.7R1. I
> have some library JS files that define objects/functions I always want
> available, but there will be specific JS files executed per servlet
> request.
>
> Right now I am using a *static block* in a servlet to do
> Context.initStandardObjects() and I save the returned Scriptable in a
> static servlet variable, called topScope, which is used when
> processing every servlet request. I want to use
> topScope.evaluateString/Reader to execute the library JS files in the
> static block, since I always want those library objects/functions
> available.
>
> Questions:
> 1) Is it safe to create topScope and call
> topScope.evaluateString/Reader in the static servlet block, then reuse
> topScope for all Servlet requests? I am concerned that when the
> context exits in the static block, some state information that was on
> that thread will be lost.
>
> 2) Any general concerns about using a static initialization block in
> the servlet? Should I just stick with doing the work mentioned above
> in the servlet init method, or even take the hit and do the work for
> each servlet request?
>
> Thank you,
> James Burke
_______________________________________________
dev-tech-js-engine-rhino mailing list
[email protected]
https://lists.mozilla.org/listinfo/dev-tech-js-engine-rhino