Skip to content

Instantly share code, notes, and snippets.

@chefhoobajoob
Last active September 23, 2018 21:16
Show Gist options
  • Save chefhoobajoob/ea20097cf441a233292e923400e9b76c to your computer and use it in GitHub Desktop.
Save chefhoobajoob/ea20097cf441a233292e923400e9b76c to your computer and use it in GitHub Desktop.
Possible solution to protecting module global data in js verticles from MT access
/* global vertx */
var logger = org.slf4j.LoggerFactory.getLogger('vertx.tests.AddingVerticle');
module.exports = {
vertxStartAsync: function (startFuture) {
logger.info('verticle started');
vertx.executeBlocking(function (blockingFuture) {
logger.debug('requiring dependency');
var adder = require('./onePlusOne');
logger.debug('creating consumer');
var config = vertx.getOrCreateContext().config();
vertx.eventBus().consumer(config.address, function(message) {
var onePlusOne = adder.add();
message.reply({result: onePlusOne })
});
blockingFuture.complete();
}, function(result, error) {
error ? startFuture.fail(error) : startFuture.complete();
})
},
vertxStop: function () {
logger.debug('verticle stopped')
}
};
/*
* Copyright 2014 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.vertx.lang.js;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Verticle;
import io.vertx.core.Vertx;
import io.vertx.core.spi.VerticleFactory;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
/**
* @author <a href="http://tfox.org">Tim Fox</a>
*/
public class JSVerticleFactory implements VerticleFactory {
static {
ClasspathFileResolver.init();
}
/**
* By default we will add an empty `process` global with an `env` property which contains the environment
* variables - this allows Vert.x to work well with libraries such as React which expect to run on Node.js
* and expect to have this global set, and which fail when it is not set.
* To disable this then provide a system property with this name and set to any value.
*/
public static final String DISABLE_NODEJS_PROCESS_ENV_PROP_NAME = "vertx.disableNodeJSProcessENV";
private static final boolean ADD_NODEJS_PROCESS_ENV = System.getProperty(DISABLE_NODEJS_PROCESS_ENV_PROP_NAME) == null;
private Vertx vertx;
private ScriptEngine engine;
@Override
public void init(Vertx vertx) { this.vertx = vertx; }
@Override
public String prefix() {
return "js";
}
@Override
public boolean blockingCreate() {
return true;
}
@Override
public Verticle createVerticle(String verticleName, ClassLoader classLoader) throws Exception {
init();
return new JSVerticle( VerticleFactory.removePrefix(verticleName ) );
}
public class JSVerticle extends AbstractVerticle {
private static final String VERTX_START_FUNCTION = "vertxStart";
private static final String VERTX_START_ASYNC_FUNCTION = "vertxStartAsync";
private static final String VERTX_STOP_FUNCTION = "vertxStop";
private static final String VERTX_STOP_ASYNC_FUNCTION = "vertxStopAsync";
private final String verticleName;
private ScriptObjectMirror newScope;
private ScriptObjectMirror futureJSClass;
private ScriptObjectMirror exports;
private JSVerticle(String verticleName) {
this.verticleName = verticleName;
}
private boolean functionExists(String functionName) {
Object som = exports.getMember(functionName);
return som != null && !som.toString().equals("undefined");
}
@Override
public void start(Future<Void> startFuture) throws Exception {
String loadJvmNpm = "load('classpath:vertx-js/util/jvm-npm.js'); this;";
String isolate = "loadWithNewGlobal({ script: \"" + loadJvmNpm + "\", name: 'isolated-jvm-npm.js' });";
newScope = (ScriptObjectMirror)engine.eval(isolate);
newScope.put("__jvertx", vertx);
futureJSClass = (ScriptObjectMirror)newScope.eval("require('vertx-js/future');");
String loadVertxGlobs = "load('classpath:vertx-js/util/vertx-globals.js'); ";
if (ADD_NODEJS_PROCESS_ENV) {
loadVertxGlobs += "load('classpath:vertx-js/util/nodejs-process-env.js'); ";
}
/*
NOTE:
When we deploy a verticle we use require.noCache as each verticle instance must have the module evaluated.
Also we run verticles in JS strict mode (with "use strict") -this means they cannot declare globals
and other restrictions. We do this for isolation.
However when doing a normal 'require' from inside a verticle we do not use strict mode as many JavaScript
modules are written poorly and would fail to run otherwise.
*/
String requireVerticle = "require.noCache('" + verticleName + "', null, true);";
exports = (ScriptObjectMirror)newScope.eval(loadVertxGlobs + requireVerticle);
if (functionExists(VERTX_START_FUNCTION)) {
exports.callMember(VERTX_START_FUNCTION);
startFuture.complete();
} else if (functionExists(VERTX_START_ASYNC_FUNCTION)) {
Object wrappedFuture = futureJSClass.newObject(startFuture);
exports.callMember(VERTX_START_ASYNC_FUNCTION, wrappedFuture);
} else {
startFuture.complete();
}
}
@Override
public void stop(Future<Void> stopFuture) throws Exception {
if (functionExists(VERTX_STOP_FUNCTION)) {
exports.callMember(VERTX_STOP_FUNCTION);
stopFuture.complete();
} else if (functionExists(VERTX_STOP_ASYNC_FUNCTION)) {
Object wrappedFuture = futureJSClass.newObject(stopFuture);
exports.callMember(VERTX_STOP_ASYNC_FUNCTION, wrappedFuture);
} else {
stopFuture.complete();
}
}
}
private synchronized void init() {
if (engine == null) {
ScriptEngineManager mgr = new ScriptEngineManager();
engine = mgr.getEngineByName("nashorn");
if (engine == null) {
throw new IllegalStateException("Cannot find Nashorn JavaScript engine - maybe you are not running with Java 8 or later?");
}
}
}
}
var process = {}; process.env=java.lang.System.getenv();
var i = 0;
module.exports = {
add: function() {
i = 0;
i += 1;
var shortly_later = new Date()/1000 + Math.random;
while( (new Date()/1000) < shortly_later) { Math.random() } //prevent optimizations
i += 1;
return i;
}
};
@RunWith(VertxUnitRunner.class)
public class Tests {
@Test
public void threadSafeJsVerticleFactoryWorks( TestContext theContext ) throws Exception {
String name = getClass().getCanonicalName() + ".concurrency";
Logger logger = LoggerFactory.getLogger( name );
Vertx vertx = Vertx.vertx();
JsonObject config = new JsonObject().put("address", VertxHost.vertxId() + ".add" );
DeploymentOptions options = new DeploymentOptions().setInstances( 8 ).setConfig( config );
logger.info("Launching AddingVerticle");
Async done = theContext.async();
JsonObject deployment = new JsonObject();
vertx.deployVerticle( "js-mt:AddingVerticle.js", options, theContext.asyncAssertSuccess( deploymentId -> {
logger.info("Done launching AddingVerticle");
deployment.put("deploymentId", deploymentId);
done.complete();
} ) );
done.awaitSuccess( 30*1000 );
if ( deployment.getString( "deploymentId" ) == null ) {
theContext.fail( "Couldn't deploy the AddingVerticle" );
return;
}
ExecutorService executor = Executors.newCachedThreadPool();
ArrayList<Future<Integer>> results = new ArrayList<>();
for(int i = 0; i < 50; i++) {
results.add(executor.submit(() -> {
Async added = theContext.async();
JsonObject addition = new JsonObject();
vertx.eventBus().<JsonObject>send(config.getString("address"), new JsonObject(), ar -> {
if ( ar.succeeded() ) {
addition.put("onePlusOne", ar.result().body().getInteger( "result" ));
}
added.complete();
});
added.awaitSuccess();
return addition.getInteger( "onePlusOne" );
}));
}
int miscalculations = 0;
int correct = 0;
for(Future<Integer> result : results) {
Integer jsResult = result.get();
if(jsResult != 2) {
logger.error("Incorrect result from js, expected 1 + 1 = 2, but got: {}", jsResult);
miscalculations += 1;
}
else { correct++; }
}
executor.awaitTermination(1, TimeUnit.SECONDS);
executor.shutdownNow();
logger.info("Overall: " + miscalculations + " wrong values for 1 + 1, " + correct + " right ones.");
assertThat(miscalculations).isEqualTo( 0 );
}
}
var Vertx = require('vertx-js/vertx'); var vertx = new Vertx(__jvertx);
var console = require('vertx-js/util/console');
var setTimeout = function(callback,delay) { return vertx.setTimer(delay, callback).doubleValue(); };
var clearTimeout = function(id) { vertx.cancelTimer(id); };
var setInterval = function(callback, delay) { return vertx.setPeriodic(delay, callback).doubleValue(); };
var clearInterval = clearTimeout;
var parent = this;
var global = this;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment