Skip to content

Commit

Permalink
Three things to improve startup time:
Browse files Browse the repository at this point in the history
  Don't load Bouncy Castle until the "crypto" module is required,
    not when the Java code is first loaded.
  Pre-compile scripts in the "util" module rather than
    compiling them on every startup.
  Use "slf4j-simple" in the pre-built JAR rather than logback.
  • Loading branch information
Gregory Brail committed Jun 18, 2015
1 parent 9705116 commit 49fdadb
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 38 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ HTTP parsing, and many other things.

### Threading Model

Each Trireme script runs in a single thread. In other words, when the script is executed, it spawns a new thread
Each Trireme script runs in a single thread. In other words, when the script is executed, it spawns a new thread
and occupies it until the script exits. Ticks and timers are implemented within that single thread. If the script
exits (has no ticks or timers, is not "pinned" by a library like http, and falls off the bottom of the code)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Copyright 2015 Apigee Corporation.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package io.apigee.trireme.core;

/**
* This interface is used to write new modules in JavaScript that are plugged in to Noderunner as built-in
* modules. Implementators must register their modules using the java.util.ServiceLoader pattern,
* by creating a file in their JAR called "META-INF/services/io.apigee.trireme.core.NodePrecompiledModule"
* and listing, one per line, the name of their implementation classes. The "rhino-compiler" module
* from Trireme is a great way to do this.
*/
public interface NodePrecompiledModule
{
/**
* Return a two-dimensional array of strings denoting the script sources. Each element must be a two-element
* array. The first element must be the name of the module, and the second must be the name of a
* class that represents a compiled Rhino script (an instance of Rhino's Script class).
* The loader will use the implementation class's classloader
* to get the compiled script.
*/
String[][] getCompiledScripts();
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import io.apigee.trireme.core.InternalNodeModule;
import io.apigee.trireme.core.NativeNodeModule;
import io.apigee.trireme.core.NodeModule;
import io.apigee.trireme.core.NodePrecompiledModule;
import io.apigee.trireme.core.NodeScriptModule;
import io.apigee.trireme.core.Utils;
import io.apigee.trireme.core.spi.NodeImplementation;
Expand Down Expand Up @@ -137,6 +138,18 @@ public void load(Context cx, ClassLoader cl)
addNativeModule(mod);
}

// Load all pre-compiled modules
ServiceLoader<NodePrecompiledModule> preLoader = ServiceLoader.load(NodePrecompiledModule.class, cl);
for (NodePrecompiledModule mod : preLoader) {
for (String[] script : mod.getCompiledScripts()) {
if (script.length != 2) {
throw new AssertionError("Script module " + mod.getClass().getName() +
" returned script source arrays that do not have two elements");
}
loadAndAdd(mod, script[0], script[1]);
}
}

// Load all JavaScript modules implemented using "NodeScriptModule"
ServiceLoader<NodeScriptModule> scriptLoader = ServiceLoader.load(NodeScriptModule.class, cl);
for (NodeScriptModule mod: scriptLoader) {
Expand All @@ -161,6 +174,26 @@ protected void addNativeModule(NodeModule mod)
}
}

private void loadAndAdd(Object impl, String name, String className)
{
try {
Class<Script> klass = (Class<Script>)impl.getClass().getClassLoader().loadClass(className);
Script script = klass.newInstance();
putCompiledModule(name, script);
} catch (ClassNotFoundException e) {
throw new AssertionError("Cannot find " + className + " from module " + impl.getClass().getName());
} catch (ClassCastException cce) {
throw new AssertionError("Cannot load " + className + " from module " +
impl.getClass().getName() + ": " + cce);
} catch (InstantiationException e) {
throw new AssertionError("Cannot load " + className + " from module " +
impl.getClass().getName() + ": " + e);
} catch (IllegalAccessException e) {
throw new AssertionError("Cannot load " + className + " from module " +
impl.getClass().getName() + ": " + e);
}
}

private void compileAndAdd(Context cx, Object impl, String name, String path)
{
String scriptSource;
Expand Down
37 changes: 7 additions & 30 deletions core/src/main/java/io/apigee/trireme/core/modules/Crypto.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
*/
package io.apigee.trireme.core.modules;

import io.apigee.trireme.core.modules.crypto.CryptoLoader;
import io.apigee.trireme.kernel.Charsets;
import io.apigee.trireme.kernel.crypto.CryptoAlgorithms;
import io.apigee.trireme.kernel.crypto.CryptoService;
Expand Down Expand Up @@ -80,13 +81,6 @@ public class Crypto
/** This is a maximum value for a byte buffer that seems to be part of V8. Used to make tests pass. */
public static final long MAX_BUFFER_LEN = 0x3fffffffL;

protected static CryptoService cryptoService;
protected static Provider cryptoProvider;

static {
loadCryptoService();
}

@Override
public String getModuleName()
{
Expand Down Expand Up @@ -136,27 +130,10 @@ public Scriptable registerExports(Context cx, Scriptable scope, NodeRuntime runt
ScriptableObject.defineClass(export, DHGroupImpl.class);
ScriptableObject.defineClass(export, ConnectionImpl.class);

return export;
}
// We need to try and initialize BouncyCastle before all of these classes will work
CryptoLoader.get();

/**
* Load the crypto service using the service loader. This is in a separate jar and loaded using the service
* loader because it depends on Bouncy Castle and we don't want that required all the time. So, we load it
* lazily here. We also load the Bouncy Castle provider (if present) so that we can manually select it
* for certain algorithms if we wish.
*/
private static void loadCryptoService()
{
ServiceLoader<CryptoService> loc = ServiceLoader.load(CryptoService.class);
if (loc.iterator().hasNext()) {
if (log.isDebugEnabled()) {
log.debug("Using crypto service implementation {}", cryptoService);
}
cryptoService = loc.iterator().next();
cryptoProvider = cryptoService.getProvider();
} else if (log.isDebugEnabled()) {
log.debug("No crypto service available");
}
return export;
}

public static ByteBuffer convertString(Object o, String encoding, Context cx, Scriptable scope)
Expand All @@ -173,17 +150,17 @@ public static ByteBuffer convertString(Object o, String encoding, Context cx, Sc

public static void ensureCryptoService(Context cx, Scriptable scope)
{
if (cryptoService == null) {
if (CryptoLoader.get().getCryptoService() == null) {
throw Utils.makeError(cx, scope, "Crypto service not available");
}
}

public static CryptoService getCryptoService() {
return cryptoService;
return CryptoLoader.get().getCryptoService();
}

public static Provider getCryptoProvider() {
return (cryptoService == null ? null : cryptoService.getProvider());
return CryptoLoader.get().getCryptoProvider();
}

public static class CryptoImpl
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* Copyright 2015 Apigee Corporation.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package io.apigee.trireme.core.modules.crypto;

import io.apigee.trireme.kernel.crypto.CryptoService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.security.Provider;
import java.util.ServiceLoader;

/**
* This is a singleton class to ensure that we only load the BouncyCastle crypto provider once
* per JVM, but for startup time reasons we don't load it until we actually require the "crypto"
* module once.
*/

public class CryptoLoader
{
private static final Logger log = LoggerFactory.getLogger(CryptoLoader.class.getName());

private CryptoService cryptoService;
private Provider cryptoProvider;

private static final CryptoLoader myself = new CryptoLoader();

private CryptoLoader()
{
ServiceLoader<CryptoService> loc = ServiceLoader.load(CryptoService.class);
if (loc.iterator().hasNext()) {
if (log.isDebugEnabled()) {
log.debug("Using crypto service implementation {}", cryptoService);
}
cryptoService = loc.iterator().next();
cryptoProvider = cryptoService.getProvider();
} else if (log.isDebugEnabled()) {
log.debug("No crypto service available");
}
}

public static CryptoLoader get() {
return myself;
}

public CryptoService getCryptoService() {
return cryptoService;
}

public Provider getCryptoProvider() {
return cryptoProvider;
}
}
4 changes: 2 additions & 2 deletions jar/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@
</dependency>

<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</dependency>
</dependencies>

Expand Down
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<useCoverage>false</useCoverage>
</properties>

Expand Down Expand Up @@ -58,6 +59,11 @@
<artifactId>slf4j-api</artifactId>
<version>1.7.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.2</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
Expand Down
27 changes: 27 additions & 0 deletions util/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,31 @@
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>io.apigee.trireme</groupId>
<artifactId>rhino-compiler</artifactId>
<version>${project.version}</version>
<executions>
<execution>
<!-- Scripts that we copied straight from node.js. Loaded as modules so we must wrap them as a function -->
<id>compile-util</id>
<configuration>
<optimizationLevel>9</optimizationLevel>
<debugInfo>true</debugInfo>
<pattern>io/apigee/trireme/util/scripts/*.js</pattern>
<codePrefix>(function (exports, require, module, __filename, __dirname) { </codePrefix>
<codePostfix>});</codePostfix>
</configuration>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
12 changes: 6 additions & 6 deletions util/src/main/java/io/apigee/trireme/util/UtilScripts.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,18 @@
*/
package io.apigee.trireme.util;

import io.apigee.trireme.core.NodeScriptModule;
import io.apigee.trireme.core.NodePrecompiledModule;

public class UtilScripts
implements NodeScriptModule
implements NodePrecompiledModule
{
@Override
public String[][] getScriptSources()
public String[][] getCompiledScripts()
{
return new String[][] {
{ "iconv", "/trireme-util/trireme-iconv.js" },
{ "node_xslt", "/trireme-util/trireme-node-xslt.js"},
{ "trireme-xslt", "/trireme-util/trireme-xslt.js"}
{ "iconv", "io.apigee.trireme.util.scripts.trireme-iconv" },
{ "node_xslt", "io.apigee.trireme.util.scripts.trireme-node-xslt"},
{ "trireme-xslt", "io.apigee.trireme.util.scripts.trireme-xslt"}
};
}
}

0 comments on commit 49fdadb

Please sign in to comment.