Skip to content

Commit

Permalink
feat: Remove multi-threading check
Browse files Browse the repository at this point in the history
  • Loading branch information
guilgaly authored and notdryft committed Jun 24, 2024
1 parent fe8407e commit 44adc42
Show file tree
Hide file tree
Showing 6 changed files with 3,075 additions and 81 deletions.
29 changes: 14 additions & 15 deletions js/core/src/gatlingJvm/callbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,25 @@ type JvmExpression<T> = (arg: JvmSession) => T;
type Callback<T, R> = (arg: T) => R;
type BiCallback<T, U, R> = (arg0: T, arg1: U) => R;

interface CallbackWrapper {
// Byte arrays
wrapByteArray(v: number[]): number[];
wrapByteArrayFunction(f: JvmExpression<number[]>): JvmExpression<number[]>;
// Generic functions
wrapFunction<T, R>(f: Callback<T, R>): Callback<T, R>;
wrapBiFunction<T, U, R>(f: BiCallback<T, U, R>): BiCallback<T, U, R>;
}

const CallbackWrapper = Java.type<CallbackWrapper>("io.gatling.js.callbacks.CallbackWrapper");
// interface CallbackWrapper {
// // Byte arrays
// wrapByteArray(v: number[]): number[];
// wrapByteArrayFunction(f: JvmExpression<number[]>): JvmExpression<number[]>;
// // Generic functions
// wrapFunction<T, R>(f: Callback<T, R>): Callback<T, R>;
// wrapBiFunction<T, U, R>(f: BiCallback<T, U, R>): BiCallback<T, U, R>;
// }
//
// const CallbackWrapper = Java.type<CallbackWrapper>("io.gatling.js.callbacks.CallbackWrapper");

// Byte arrays

export const wrapByteArray = (v: number[]) => CallbackWrapper.wrapByteArray(v);
export const wrapByteArray = (v: number[]) => v;

export const wrapByteArrayCallback = (f: JvmExpression<number[]>) => CallbackWrapper.wrapByteArrayFunction(f);
export const wrapByteArrayCallback = (f: JvmExpression<number[]>) => f;

// Generic functions

export const wrapCallback = <T, R>(f: Callback<T, R>): Callback<T, R> => CallbackWrapper.wrapFunction(f);
export const wrapCallback = <T, R>(f: Callback<T, R>): Callback<T, R> => f;

export const wrapBiCallback = <T, U, R>(f: BiCallback<T, U, R>): BiCallback<T, U, R> =>
CallbackWrapper.wrapBiFunction(f);
export const wrapBiCallback = <T, U, R>(f: BiCallback<T, U, R>): BiCallback<T, U, R> => f;
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2011-2024 GatlingCorp (https://gatling.io)
*
* Licensed 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 com.oracle.truffle.js.lang;

import static io.gatling.internal.asm.Opcodes.*;

import io.gatling.internal.asm.*;
import java.io.IOException;
import java.lang.invoke.MethodHandles;

public class JavaScriptLanguageHack {
public static void allowThreadAccess() throws IOException, IllegalAccessException {
final byte[] modifiedJavaScriptLanguage;

try (final var classInputStream =
ClassLoader.getSystemClassLoader()
.getResourceAsStream("com/oracle/truffle/js/lang/JavaScriptLanguage.class")) {
if (classInputStream == null) {
throw new IllegalStateException(
"JavaScriptLanguage class file not found: GraalJS dependency is probably missing");
}

final ClassReader classReader = new ClassReader(classInputStream);
final var classWriter =
new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
ClassVisitor classVisitor = new JavaScriptLanguageClassAdapter(classWriter) {};
classReader.accept(classVisitor, 0);
modifiedJavaScriptLanguage = classWriter.toByteArray();
}

MethodHandles.lookup().defineClass(modifiedJavaScriptLanguage);
}

private static class JavaScriptLanguageClassAdapter extends ClassVisitor {
public JavaScriptLanguageClassAdapter(ClassVisitor cv) {
super(ASM9, cv);
}

@Override
public void visitEnd() {
final var mv =
cv.visitMethod(
ACC_PROTECTED, "isThreadAccessAllowed", "(Ljava/lang/Thread;Z)Z", null, null);
mv.visitCode();
mv.visitInsn(ICONST_1);
mv.visitInsn(IRETURN);
mv.visitMaxs(1, 2);
mv.visitEnd();

super.visitEnd();
}
}
}
77 changes: 11 additions & 66 deletions jvm/adapter/src/main/java/io/gatling/js/JsSimulation.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,80 +16,25 @@

package io.gatling.js;

import com.oracle.truffle.js.runtime.JSContextOptions;
import io.gatling.javaapi.core.PopulationBuilder;
import com.oracle.truffle.js.lang.JavaScriptLanguageHack;
import io.gatling.javaapi.core.Simulation;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.TypeLiteral;
import org.graalvm.polyglot.Value;

public class JsSimulation extends Simulation {
private static final String JS = "js";

public JsSimulation() {
var simulationName =
Optional.ofNullable(System.getProperty("gatling.js.simulation"))
.orElseThrow(
() ->
new NoSuchElementException(
"System property gatling.js.simulation must be defined"));
var bundleUrl =
Optional.ofNullable(System.getProperty("gatling.js.bundle.filePath"))
.map(this::filePathToUrl)
.or(
() ->
Optional.ofNullable(System.getProperty("gatling.js.bundle.resourcePath"))
.map(this::resourcePathToUrl))
.orElseThrow(
() ->
new NoSuchElementException(
"One of the system properties gatling.js.bundle.filePath or gatling.js.bundle.resourcePath must be defined"));

// Context is never closed because it will live for the entire duration of the process
var context =
Context.newBuilder(JS)
.allowAllAccess(true)
.option(JSContextOptions.STRICT_NAME, "true")
.build();

static {
try {
context.eval(Source.newBuilder(JS, bundleUrl).mimeType("application/javascript").build());
} catch (IOException e) {
throw new IllegalStateException("Cannot load Javascript bundle file at " + bundleUrl, e);
// Implemented in as separate class because Lookup#defineClass() needs to be called from the
// same package as the class being defined
JavaScriptLanguageHack.allowThreadAccess();
} catch (IOException | IllegalAccessException e) {
throw new RuntimeException(e);
}
Value jsIifeWrapper = context.getBindings(JS).getMember("gatling");
Value jsSimulationValue = jsIifeWrapper.getMember(simulationName);
if (jsSimulationValue == null) {
throw new NoSuchElementException(
"Simulation '" + simulationName + "' was not found in the JavaScript bundle");
}

jsSimulationValue
.as(new TypeLiteral<Consumer<Function<List<PopulationBuilder>, SetUp>>>() {})
.accept(this::setUp);
}

private URL filePathToUrl(String filePath) {
try {
return Paths.get(filePath).toUri().toURL();
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Not a valid file path: " + filePath, e);
}
}

private URL resourcePathToUrl(String resourcePath) {
var url = getClass().getClassLoader().getResource(resourcePath);
if (url == null) {
throw new IllegalArgumentException("Not a valid resource path: " + resourcePath);
}
return url;
public JsSimulation() {
// Implemented in a separate class to defer loading any GraalJS class until after the modified
// class has been loaded in this class's static block
JsSimulationHelper.loadSimulation(this::setUp);
}
}
95 changes: 95 additions & 0 deletions jvm/adapter/src/main/java/io/gatling/js/JsSimulationHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2011-2024 GatlingCorp (https://gatling.io)
*
* Licensed 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.gatling.js;

import com.oracle.truffle.js.runtime.JSContextOptions;
import io.gatling.javaapi.core.PopulationBuilder;
import io.gatling.javaapi.core.Simulation;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.TypeLiteral;
import org.graalvm.polyglot.Value;

public class JsSimulationHelper {
private static final String JS = "js";

public static void loadSimulation(Function<List<PopulationBuilder>, Simulation.SetUp> setUp) {
var simulationName =
Optional.ofNullable(System.getProperty("gatling.js.simulation"))
.orElseThrow(
() ->
new NoSuchElementException(
"System property gatling.js.simulation must be defined"));
var bundleUrl =
Optional.ofNullable(System.getProperty("gatling.js.bundle.filePath"))
.map(JsSimulationHelper::filePathToUrl)
.or(
() ->
Optional.ofNullable(System.getProperty("gatling.js.bundle.resourcePath"))
.map(JsSimulationHelper::resourcePathToUrl))
.orElseThrow(
() ->
new NoSuchElementException(
"One of the system properties gatling.js.bundle.filePath or gatling.js.bundle.resourcePath must be defined"));

// Context is never closed because it will live for the entire duration of the process
var context =
Context.newBuilder(JS)
.allowAllAccess(true)
.option(JSContextOptions.STRICT_NAME, "true")
.build();

try {
context.eval(Source.newBuilder(JS, bundleUrl).mimeType("application/javascript").build());
} catch (IOException e) {
throw new IllegalStateException("Cannot load Javascript bundle file at " + bundleUrl, e);
}
Value jsIifeWrapper = context.getBindings(JS).getMember("gatling");
Value jsSimulationValue = jsIifeWrapper.getMember(simulationName);
if (jsSimulationValue == null) {
throw new NoSuchElementException(
"Simulation '" + simulationName + "' was not found in the JavaScript bundle");
}

jsSimulationValue
.as(new TypeLiteral<Consumer<Function<List<PopulationBuilder>, Simulation.SetUp>>>() {})
.accept(setUp);
}

private static URL filePathToUrl(String filePath) {
try {
return Paths.get(filePath).toUri().toURL();
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Not a valid file path: " + filePath, e);
}
}

private static URL resourcePathToUrl(String resourcePath) {
var url = JsSimulationHelper.class.getClassLoader().getResource(resourcePath);
if (url == null) {
throw new IllegalArgumentException("Not a valid resource path: " + resourcePath);
}
return url;
}
}
Loading

0 comments on commit 44adc42

Please sign in to comment.