diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index ed20a92395cd..a972a4166d85 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -14,6 +14,14 @@ jobs: with: distribution: 'temurin' java-version: '21.0.3' + - name: Setup GraalVM + uses: graalvm/setup-graalvm@v1 + with: + java-version: '21.0.3' + distribution: 'graalvm' + components: 'native-image' + github-token: ${{ secrets.GITHUB_TOKEN }} + set-java-home: 'false' - name: Set version env variable run: | echo "VERSION=$(./gradlew properties | grep ^version: | cut -d\ -f2 | sed 's@-SNAPSHOT@@')" >> $GITHUB_ENV @@ -28,8 +36,16 @@ jobs: git config user.name ${{ secrets.BALLERINA_BOT_USERNAME }} git config user.email ${{ secrets.BALLERINA_BOT_EMAIL }} git checkout -b release-${VERSION} - ./gradlew release -Prelease.useAutomaticVersion=true + ./gradlew build -Pversion=${VERSION} ./gradlew -Pversion=${VERSION} publish -x test --continue + - name: Create Release Tag + run: | + git config --local user.name ${{ secrets.BALLERINA_BOT_USERNAME }} + git config --local user.email ${{ secrets.BALLERINA_BOT_EMAIL }} + git tag -a v${VERSION} -m "Release v${VERSION}" + git push origin v${VERSION} + env: + GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }} - name: Create Github release from the release tag run: | curl --request POST 'https://api.github.com/repos/ballerina-platform/ballerina-lang/releases' \ diff --git a/.github/workflows/pull_request_full_build.yml b/.github/workflows/pull_request_full_build.yml index 4a208fac63f2..f6a4240f811a 100644 --- a/.github/workflows/pull_request_full_build.yml +++ b/.github/workflows/pull_request_full_build.yml @@ -4,6 +4,8 @@ on: pull_request: branches: - master + - 2201.[0-9]+.x + - 2201.[0-9]+.[0-9]+-stage jobs: build-lang: @@ -59,7 +61,7 @@ jobs: strategy: fail-fast: false matrix: - level: [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] + level: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] steps: - name: Checkout Repository diff --git a/bvm/ballerina-runtime/README.md b/bvm/ballerina-runtime/README.md index 6b0526f3512d..933b63a2af41 100644 --- a/bvm/ballerina-runtime/README.md +++ b/bvm/ballerina-runtime/README.md @@ -45,17 +45,17 @@ Dependency versions can be found [here](https://github.com/ballerina-platform/ba ## Ballerina Java Runtime API Ballerina runtime API will contain the following sub packages. -| **Package** | **Description** | -|---------------------------------------|--------------------------------------------------| -| io.ballerina.runtime.api | Basic runtime constructs | -| io.ballerina.runtime.api.concurrent | Handle Ballerina asynchronous related constructs | -| io.ballerina.runtime.api.constants | Runtime constants | -| io.ballerina.runtime.api.creators | APIs to create types, values, etc. | -| io.ballerina.runtime.api.flags | Runtime flags | -| io.ballerina.runtime.api.launch | Constructs for startup runtime | -| io.ballerina.runtime.api.types | Represent Ballerina Java types | -| io.ballerina.runtime.api.utils | Utils methods | -| io.ballerina.runtime.api.utils.values | Represent Ballerina Java values | +| **Package** | **Description** | +|-------------------------------------|-----------------------------------------------------| +| io.ballerina.runtime.api | Basic runtime constructs | +| io.ballerina.runtime.api.concurrent | Handle Ballerina-related asynchronous constructs | +| io.ballerina.runtime.api.constants | Runtime constants | +| io.ballerina.runtime.api.creators | APIs to create types, values, errors, etc. | +| io.ballerina.runtime.api.flags | Runtime flags for types and symbols | +| io.ballerina.runtime.api.repository | Runtime repository constructs for remote-management | +| io.ballerina.runtime.api.types | Represent Ballerina Java types | +| io.ballerina.runtime.api.utils | Utils methods | +| io.ballerina.runtime.api.values | Represent Ballerina Java values | ## Map Java types to Ballerina types The following table summarizes how Java types are mapped to corresponding Ballerina types. This is applicable when mapping a return type of a Java method to a Ballerina type. @@ -98,13 +98,12 @@ The following table summarizes how Ballerina types are mapped to corresponding J ## Main API constructs -| **Construct** | **Description** | -|------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| io.ballerina.runtime.api.Environment | Developers can use this as the first argument of an interop method, Ballerina will inject an instance of `Environment` class when calling. That instance can be used to communicate with the currently executing Ballerina runtime. With `Environment` instance, you can get interop Ballerina function name, path parameters, strand id, strand metadata, current module, current runtime, etc. | -| io.ballerina.runtime.api.Future | This will contain the future value once we call the Ballerina method from API asynchronously. | -| io.ballerina.runtime.api.Module | Represent Java runtime module. | +| **Construct** | **Description** | +|------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| io.ballerina.runtime.api.Environment | Developers can use this as the first argument of an interop method, Ballerina will inject an instance of `Environment` class when calling. That instance can be used to communicate with the currently executing Ballerina runtime. With `Environment` instance, you can get interop Ballerina function name, path parameters, strand id, strand metadata, current module, current runtime, etc. | +| io.ballerina.runtime.api.Module | Represent Java runtime module. | | io.ballerina.runtime.api.types.PredefinedTypes | Contains predefined types. | -| io.ballerina.runtime.api.Runtime | An instance of the current runtime can be obtained through an `Environment` instance. This will contain APIs to call Ballerina object methods asynchronously. | +| io.ballerina.runtime.api.Runtime | An instance of the current runtime can be obtained through an `Environment` instance. This will contain APIs to call Ballerina object methods asynchronously. | | io.ballerina.runtime.api.types.TypeTags | Contains runtime type tags. | ## Create a Ballerina value @@ -134,23 +133,16 @@ types will not work correctly in the Ballerina code since those type definitions Those can only be used for Java unit tests. -## Calling a Ballerina object method - -Ballerina runtime exposes APIs to call a Ballerina object method and Ballerina function pointer using Java. - -`io.ballerina.runtime.api.Runtime` class exposes two Java APIs to call object methods. - -1. `invokeMethodAsyncSequentially` - -Invoke the object method asynchronously and sequentially. This method will ensure that the object methods are invoked in the same thread where other object methods are executed. So, the methods will be executed sequentially per object level. +## Calling Ballerina object methods and functions -2. `invokeMethodAsyncConcurrently` +Ballerina runtime exposes APIs to call Ballerina object methods and functions using Java. If the method is called with an available running strand, the method will be executed in the same strand. The caller can decide whether to execute the method concurrently or sequentially using the `StrandMetadata` argument. +The `isConcurrentSafe` property of the `StrandMetadata` argument should be set to `true` only if the caller guarantees that the mutable state is free of data races for the given method and arguments, allowing the method to run concurrently. -Invoke the object method asynchronously and concurrently. The caller needs to ensure that no data race is possible for the mutable state with a given object method and arguments so that the method can be concurrently run with different OS threads. +## Calling a Ballerina object method ->**Note:** If the caller can ensure that the given object and object method are isolated and no data race is possible for the mutable state with given arguments, use `invokeMethodAsyncConcurrently`, otherwise use `invokeMethodAsyncSequentially`. We can decide the object method isolation if and only if both `object.getType().isIsolated()` and `object.getType().isIsolated(methodName)` return `true`. +Ballerina runtime exposes `callMethod` API to call a Ballerina object method using the `io.ballerina.runtime.api.Runtime` class. -The following code shows an example of calling an isolated method using Java API. +The following code shows an example of calling an object method using Java API. #### Ballerina @@ -178,7 +170,7 @@ public class Person { public function main() { Person p = new Person("John", 30); - string sport = p.callPlay("football"); + string sport = p.callPlayWithArgs("football"); } ``` @@ -186,27 +178,56 @@ public function main() { ```java class Test { - public static BString callPlay(Environment env, BObject object, BString bString) { - env.getRuntime().invokeMethodAsyncConcurrently(object, "play", "play", null, - new Callback() { - @Override - public void notifySuccess(Object result) { - future.complete(result); - } - - @Override - public void notifyFailure(BError error) { - future.complete(error); - } - }, null, PredefinedTypes.TYPE_STRING, bString, true); - return null; - } + public static BString callPlayWithArgs(Environment env, BObject object, BString bString) { + return env.getRuntime().callMethod(object, "play", null, bString); + } +} +``` + +## Calling a Ballerina function + +Ballerina runtime exposes APIs to call a Ballerina function using Java. This can be done in two ways that are described below. + +### Calling a Function using `io.ballerina.runtime.api.Runtime` class + +Developers can call a Ballerina function using the `io.ballerina.runtime.api.Runtime` class. This class exposes the `callFunction` method to call a Ballerina function. + +E.g. + +#### Ballerina + +The following Ballerina code defines a function in module `foo` from organization `testOrg`. + +```ballerina + +public function main() returns error? { + boolean b = callIsEven(2); +} + +function isEven(int n) returns boolean { + return n % 2 == 0; +} + +public isolated function callIsEven(int n) returns any = @java:Method { + 'class: "org.ballerinalang.examples.Test", + name: "callIsEven" +} external; +``` + +#### Java + +```java + +class Test { + public static Object callIsEven(Environment env, long arg) { + return env.getRuntime().callFunction(new Module("testOrg", "foo"), "isEven", null, arg); + } } ``` ### Calling a Function Pointer -Developers can call a function through a function pointer which passes through an interop function. Runtime exposes the `asyncCall` method in `io.ballerina.runtime.api.values.BFunctionPointer` class. +Developers can call a function through a function pointer which passes through an interop function. Runtime exposes the `call` method in `io.ballerina.runtime.api.values.BFunctionPointer` class. E.g. @@ -233,15 +254,9 @@ public isolated function invokeFunctionPointer(function func, any|error... args) ```java class Test { - public static Object invokeFunctionPointer(Object func, Object[] args) { + public static Object invokeFunctionPointer(Environment env, Object func, Object[] args) { BFunctionPointer function = (BFunctionPointer) func; - List argList = new ArrayList<>(); - for (Object arg : args) { - argList.add(arg); - argList.add(true); // Due to a limitation in the current API we need to pass `true` as every other - // parameter value to handle default values. - } - return function.asyncCall(argList.toArray(), o -> o, METADATA); + return function.call(env.getRuntime(), args); } } -``` +``` \ No newline at end of file diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/BalRuntime.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/BalRuntime.java index 17f3cdb14cc8..50e39cd1f490 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/BalRuntime.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/BalRuntime.java @@ -30,10 +30,8 @@ import io.ballerina.runtime.internal.errors.ErrorCodes; import io.ballerina.runtime.internal.errors.ErrorHelper; import io.ballerina.runtime.internal.launch.LaunchUtils; -import io.ballerina.runtime.internal.scheduling.AsyncUtils; import io.ballerina.runtime.internal.scheduling.RuntimeRegistry; import io.ballerina.runtime.internal.scheduling.Scheduler; -import io.ballerina.runtime.internal.values.FutureValue; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -74,9 +72,7 @@ public Object init() { handleAlreadyCalled(moduleInitialized, "init"); try { this.invokeConfigInit(); - FutureValue future = scheduler.startIsolatedFunction(rootModule, "$moduleInit", - new StrandMetadata(true, null)); - Object result = AsyncUtils.getFutureResult(future.completableFuture); + Object result = scheduler.callFunction(rootModule, "$moduleInit", new StrandMetadata(true, null)); this.moduleInitialized = true; return result; } catch (ClassNotFoundException e) { @@ -212,11 +208,11 @@ private void validateArgs(BObject object, String methodName) { } } - Object invokeModuleStop() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, + void invokeModuleStop() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class configClass = loadClass(MODULE_INIT_CLASS_NAME); Method method = configClass.getDeclaredMethod("$currentModuleStop", BalRuntime.class); - return method.invoke(null, this); + method.invoke(null, this); } protected Class loadClass(String className) throws ClassNotFoundException { diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/scheduling/Scheduler.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/scheduling/Scheduler.java index a7b657dceba5..c8e2dfa8150c 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/scheduling/Scheduler.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/scheduling/Scheduler.java @@ -34,7 +34,6 @@ import io.ballerina.runtime.api.values.BNever; import io.ballerina.runtime.api.values.BObject; import io.ballerina.runtime.internal.BalRuntime; -import io.ballerina.runtime.internal.types.BFunctionType; import io.ballerina.runtime.internal.types.BServiceType; import io.ballerina.runtime.internal.utils.ErrorUtils; import io.ballerina.runtime.internal.values.FPValue; @@ -68,73 +67,75 @@ public static Strand getStrand() { return strandHolder.get().strand; } public Object callFunction(Module module, String functionName, StrandMetadata metadata, Object... args) { - Strand parentStrand = Scheduler.getStrand(); - if (parentStrand != null) { - boolean runnable = parentStrand.isRunnable(); - if (!runnable) { - parentStrand.resume(); - } - ValueCreatorAndFunctionType functionType = getGetValueCreatorAndFunctionType(module, functionName); - Object[] argsWithDefaultValues = getArgsWithDefaultValues(functionType.valueCreator(), - functionType.functionType(), parentStrand, args); - Object result = functionType.valueCreator().call(parentStrand, functionName, argsWithDefaultValues); - if (!runnable) { - parentStrand.yield(); - } - return result; + Strand strand = Scheduler.getStrand(); + Map properties = null; + boolean isIsolated = false; + if (metadata != null) { + properties = metadata.properties(); + isIsolated = metadata.isConcurrentSafe(); } - if (metadata != null && metadata.isConcurrentSafe()) { - return AsyncUtils.getFutureResult( - startIsolatedFunction(module, functionName, metadata, args).completableFuture); + if (strand == null) { + strand = createStrand(null, functionName, isIsolated, properties, null); + strandHolder.get().strand = strand; + } + if (strand.isRunnable()) { + return callFunction(module, functionName, args, strand); + } + try { + strand.resume(); + return callFunction(module, functionName, args, strand); + } finally { + strand.done(); } - return AsyncUtils.getFutureResult(startNonIsolatedFunction(module, functionName, metadata, args) - .completableFuture); } public Object callMethod(BObject object, String methodName, StrandMetadata metadata, Object... args) { - Strand parentStrand = Scheduler.getStrand(); - if (parentStrand != null) { - boolean runnable = parentStrand.isRunnable(); - if (!runnable) { - parentStrand.resume(); - } - ObjectType objectType = (ObjectType) TypeUtils.getImpliedType(object.getOriginalType()); - MethodType methodType = getObjectMethodType(methodName, objectType); - Object[] argsWithDefaultValues = getArgsWithDefaultValues(objectType, methodType, parentStrand, args); - Object result = ((ObjectValue) object).call(parentStrand, methodName, argsWithDefaultValues); - if (!runnable) { - parentStrand.yield(); - } - return result; + Strand strand = Scheduler.getStrand(); + Map properties = null; + boolean isIsolated = false; + if (metadata != null) { + properties = metadata.properties(); + isIsolated = metadata.isConcurrentSafe(); } - if (metadata != null && metadata.isConcurrentSafe()) { - return AsyncUtils.getFutureResult(startIsolatedMethod(object, methodName, metadata, args) - .completableFuture); + if (strand == null) { + String strandName = getStrandName(object, methodName); + strand = createStrand(null, strandName, isIsolated, properties, null); + strandHolder.get().strand = strand; + } + + if (strand.isRunnable()) { + return callMethod(object, methodName, args, strand); + } + try { + strand.resume(); + return callMethod(object, methodName, args, strand); + } finally { + strand.done(); } - return AsyncUtils.getFutureResult(startNonIsolatedMethod(object, methodName, metadata, args) - .completableFuture); } public Object callFP(FPValue fp, StrandMetadata metadata, Object... args) { - Strand parentStrand = Scheduler.getStrand(); - if (parentStrand != null) { - boolean runnable = parentStrand.isRunnable(); - if (!runnable) { - parentStrand.resume(); - } - FunctionType functionType = (FunctionType) TypeUtils.getImpliedType(TypeUtils.getType(fp)); - Object[] argsWithDefaultValues = getArgsWithDefaultValues(parentStrand, args, functionType); - Object[] argsWithStrand = getArgsWithStrand(parentStrand, argsWithDefaultValues); - Object result = fp.function.apply(argsWithStrand); - if (!runnable) { - parentStrand.yield(); - } - return result; + Strand strand = Scheduler.getStrand(); + Map properties = null; + boolean isIsolated = false; + if (metadata != null) { + properties = metadata.properties(); + isIsolated = metadata.isConcurrentSafe(); + } + if (strand == null) { + String strandName = getStrandName(fp.getName()); + strand = createStrand(null, strandName, isIsolated, properties, null); + strandHolder.get().strand = strand; } - if (metadata != null && metadata.isConcurrentSafe()) { - return AsyncUtils.getFutureResult(startIsolatedFP(fp, metadata, args).completableFuture); + if (strand.isRunnable()) { + return callFp(fp, args, strand); + } + try { + strand.resume(); + return callFp(fp, args, strand); + } finally { + strand.done(); } - return AsyncUtils.getFutureResult(startNonIsolatedFP(fp, metadata, args).completableFuture); } @SuppressWarnings("unused") @@ -181,131 +182,26 @@ public FutureValue startNonIsolatedWorker(FPValue fp, Strand parentStrand, Type return future; } - public FutureValue startIsolatedFunction(Module module, String functionName, StrandMetadata metadata, - Object... args) { - ValueCreator valueCreator = ValueCreator.getValueCreator(ValueCreator.getLookupKey(module.getOrg(), - module.getName(), module.getMajorVersion(), module.isTestPkg())); - FunctionType functionType = valueCreator.getFunctionType(functionName); - FutureValue future = createFutureWithMetadata(null, functionName, true, functionType.getReturnType(), - metadata, null); - Object[] argsWithDefaultValues = getArgsWithDefaultValues(valueCreator, functionType, future.strand, args); - Thread.startVirtualThread(() -> { - try { - strandHolder.get().strand = future.strand; - Object result = valueCreator.call(future.strand, functionName, argsWithDefaultValues); - future.completableFuture.complete(result); - } catch (Throwable t) { - future.completableFuture.completeExceptionally(ErrorUtils.createErrorFromThrowable(t)); - } - }).setName(future.strand.name); - return future; - } - - private FutureValue startIsolatedMethod(BObject object, String methodName, StrandMetadata metadata, - Object... args) { - String strandName = getStrandName(object, methodName); - ObjectType objectType = (ObjectType) TypeUtils.getImpliedType(object.getOriginalType()); - MethodType methodType = getObjectMethodType(methodName, objectType); - FutureValue future = createFutureWithMetadata(null, strandName, true, methodType.getReturnType(), metadata, - null); - Object[] argsWithDefaultValues = getArgsWithDefaultValues(objectType, methodType, future.strand, args); - Thread.startVirtualThread(() -> { - try { - strandHolder.get().strand = future.strand; - Object result = ((ObjectValue) object).call(future.strand, methodName, argsWithDefaultValues); - future.completableFuture.complete(result); - } catch (Throwable t) { - future.completableFuture.completeExceptionally(ErrorUtils.createErrorFromThrowable(t)); - } - }).setName(future.strand.name); - return future; - } - - private FutureValue startIsolatedFP(FPValue fp, StrandMetadata metadata, Object... args) { - BFunctionType functionType = (BFunctionType) fp.getType(); - FutureValue future = createFutureWithMetadata(null, fp.getName(), true, functionType.getReturnType(), - metadata, null); - Object[] argsWithDefaultValues = getArgsWithDefaultValues(future.strand, args, functionType); - Object[] argsWithStrand = getArgsWithStrand(future.strand, argsWithDefaultValues); - Thread.startVirtualThread(() -> { - try { - strandHolder.get().strand = future.strand; - Object result = fp.function.apply(argsWithStrand); - future.completableFuture.complete(result); - } catch (Throwable t) { - future.completableFuture.completeExceptionally(ErrorUtils.createErrorFromThrowable(t)); - } - }).setName(future.strand.name); - return future; - } - - - private FutureValue startNonIsolatedFunction(Module module, String functionName, StrandMetadata metadata, - Object... args) { - ValueCreator valueCreator = ValueCreator.getValueCreator(ValueCreator.getLookupKey(module)); - FunctionType functionType = valueCreator.getFunctionType(functionName); - FutureValue future = - createFutureWithMetadata(null, functionName, false, functionType.getReturnType(), metadata, null); - Object[] argsWithDefaultValues = getArgsWithDefaultValues(valueCreator, functionType, future.strand, args); - Thread.startVirtualThread(() -> { - try { - future.strand.resume(); - strandHolder.get().strand = future.strand; - Object result = valueCreator.call(future.strand, functionName, argsWithDefaultValues); - future.completableFuture.complete(result); - } catch (Throwable t) { - future.completableFuture.completeExceptionally(ErrorUtils.createErrorFromThrowable(t)); - } finally { - future.strand.done(); - } - }).setName(future.strand.name); - return future; + private Object callFunction(Module module, String functionName, Object[] args, Strand parentStrand) { + ValueCreatorAndFunctionType functionType = getGetValueCreatorAndFunctionType(module, functionName); + Object[] argsWithDefaultValues = getArgsWithDefaultValues(functionType.valueCreator(), + functionType.functionType(), parentStrand, args); + return functionType.valueCreator().call(parentStrand, functionName, argsWithDefaultValues); } - private FutureValue startNonIsolatedMethod(BObject object, String methodName, StrandMetadata metadata, - Object... args) { - String strandName = getStrandName(object, methodName); + private Object callMethod(BObject object, String methodName, Object[] args, Strand parentStrand) { ObjectType objectType = (ObjectType) TypeUtils.getImpliedType(object.getOriginalType()); MethodType methodType = getObjectMethodType(methodName, objectType); - FutureValue future = createFutureWithMetadata(null, strandName, false, methodType.getReturnType(), metadata, - null); - Object[] argsWithDefaultValues = getArgsWithDefaultValues(objectType, methodType, future.strand, args); - Thread.startVirtualThread(() -> { - try { - future.strand.resume(); - strandHolder.get().strand = future.strand; - Object result = ((ObjectValue) object).call(future.strand, methodName, argsWithDefaultValues); - future.completableFuture.complete(result); - } catch (Throwable t) { - future.completableFuture.completeExceptionally(ErrorUtils.createErrorFromThrowable(t)); - } finally { - future.strand.done(); - } - }).setName(future.strand.name); - return future; + Object[] argsWithDefaultValues = getArgsWithDefaultValues(objectType, methodType, parentStrand, args); + return ((ObjectValue) object).call(parentStrand, methodName, argsWithDefaultValues); } - private FutureValue startNonIsolatedFP(FPValue fp, StrandMetadata metadata, Object... args) { - BFunctionType functionType = (BFunctionType) fp.getType(); - String strandName = getStrandName("$anon", fp.getName()); - FutureValue future = createFutureWithMetadata(null, strandName, true, functionType.getReturnType(), metadata, - null); - Object[] argsWithDefaultValues = getArgsWithDefaultValues(future.strand, args, functionType); - Object[] argsWithStrand = getArgsWithStrand(future.strand, argsWithDefaultValues); - Thread.startVirtualThread(() -> { - try { - future.strand.resume(); - strandHolder.get().strand = future.strand; - Object result = fp.function.apply(argsWithStrand); - future.completableFuture.complete(result); - } catch (Throwable t) { - future.completableFuture.completeExceptionally(ErrorUtils.createErrorFromThrowable(t)); - } finally { - future.strand.done(); - } - }).setName(future.strand.name); - return future; + private Object callFp(FPValue fp, Object[] args, Strand parentStrand) { + FunctionType functionType = (FunctionType) TypeUtils.getImpliedType(TypeUtils.getType(fp)); + Object[] argsWithDefaultValues = getArgsWithDefaultValues(parentStrand, args, functionType); + Object[] argsWithStrand = getArgsWithStrand(parentStrand, argsWithDefaultValues); + return fp.function.apply(argsWithStrand); } private Object[] getArgsWithDefaultValues(Strand parentStrand, Object[] args, FunctionType functionType) { @@ -438,9 +334,9 @@ private FutureValue createFuture(Type constraint, Strand newStrand) { return new FutureValue(newStrand, constraint); } - private static String getStrandName(String functionName, String strandName) { + private static String getStrandName(String strandName) { if (strandName == null) { - strandName = functionName; + strandName = "$anon"; } return strandName; } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/troubleshoot/StrandDump.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/troubleshoot/StrandDump.java index 08dc8d7e545a..729e9bb5467a 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/troubleshoot/StrandDump.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/troubleshoot/StrandDump.java @@ -28,7 +28,9 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import java.util.regex.Pattern; @@ -45,10 +47,10 @@ public final class StrandDump { private static final String WORKING_DIR = System.getProperty("user.dir") + "/"; private static final String FILENAME = "threadDump" + LocalDateTime.now(); private static final String VIRTUAL_THREAD_IDENTIFIER = "virtual"; - private static final String ISOLATED_WORKER_IDENTIFIER = "io.ballerina.runtime.internal.scheduling." + - "Scheduler.lambda$startIsolatedWorker"; - private static final String NON_ISOLATED_WORKER_IDENTIFIER = "io.ballerina.runtime.internal.scheduling." + - "Scheduler.lambda$startNonIsolatedWorker"; + private static final String ISOLATED_IDENTIFIER = "io.ballerina.runtime.internal.scheduling." + + "Scheduler.lambda$startIsolated"; + private static final String NON_ISOLATED_IDENTIFIER = "io.ballerina.runtime.internal.scheduling." + + "Scheduler.lambda$startNonIsolated"; private static final String JAVA_TRACE_PATTERN = "java\\.|\\.java(?::\\d+)?"; // .java, java., .java:(any number) private static final String BAL_TRACE_PATTERN = "\\.bal:\\d+"; // .bal:(any number) private static volatile HotSpotDiagnosticMXBean hotSpotDiagnosticMXBean; @@ -69,16 +71,16 @@ public static String getStrandDump() { private static String generateOutput(String dump) { String[] dumpItems = dump.split("\\n\\n"); int id = 0; - Set isolatedWorkerList = new HashSet<>(); - Set nonIsolatedWorkerList = new HashSet<>(); - ArrayList> balTraces = new ArrayList<>(); + Set isolatedStrandList = new HashSet<>(); + Set nonIsolatedStrandList = new HashSet<>(); + Map> balTraces = new HashMap<>(); Pattern javaPattern = Pattern.compile(JAVA_TRACE_PATTERN); Pattern balPattern = Pattern.compile(BAL_TRACE_PATTERN); for (String item : dumpItems) { String[] lines = item.split("\\n"); String[] subitems = lines[0].split("\" "); ArrayList balTraceItems = new ArrayList<>(); - boolean balStrand = false; + boolean isBalStrand = false; if (subitems.length > 1 && subitems[1].equals(VIRTUAL_THREAD_IDENTIFIER)) { balTraceItems.add("\tStrand " + lines[0].replace(VIRTUAL_THREAD_IDENTIFIER, ":") + "\n\t\tat"); String prefix = " "; @@ -87,21 +89,21 @@ private static String generateOutput(String dump) { balTraceItems.add(prefix + line + "\n"); prefix = "\t\t "; if (balPattern.matcher(line).find()) { - balStrand = true; + isBalStrand = true; } } else { - if (line.contains(ISOLATED_WORKER_IDENTIFIER)) { - isolatedWorkerList.add(id); - } else if (line.contains(NON_ISOLATED_WORKER_IDENTIFIER)) { - nonIsolatedWorkerList.add(id); + if (line.contains(ISOLATED_IDENTIFIER)) { + isolatedStrandList.add(id); + } else if (line.contains(NON_ISOLATED_IDENTIFIER)) { + nonIsolatedStrandList.add(id); } } } - if (balStrand) { - balTraces.add(balTraceItems); + if (isBalStrand) { + balTraces.put(id, balTraceItems); } else { - isolatedWorkerList.remove(id); - nonIsolatedWorkerList.remove(id); + isolatedStrandList.remove(id); + nonIsolatedStrandList.remove(id); } id++; } @@ -112,19 +114,23 @@ private static String generateOutput(String dump) { outputStr.append(dateTimeFormatter.format(localDateTime)); outputStr.append("]\n===============================================================\n\n"); outputStr.append("Total Strand count \t\t\t:\t").append(balTraces.size()).append("\n\n"); - outputStr.append("Total Isolated Worker count \t\t:\t").append(isolatedWorkerList.size()).append("\n\n"); - outputStr.append("Total Non Isolated Worker count \t\t:\t").append(nonIsolatedWorkerList.size()). + outputStr.append("Total Isolated Strand count \t\t:\t").append(isolatedStrandList.size()).append("\n\n"); + outputStr.append("Total Non Isolated Strand count \t\t:\t").append(nonIsolatedStrandList.size()). append("\n\n"); outputStr.append("================================================================\n"); - outputStr.append("\nIsolated Workers:\n\n"); - for (int strandId: isolatedWorkerList) { - balTraces.get(strandId).forEach(outputStr::append); - outputStr.append("\n"); + outputStr.append("\nIsolated Strands:\n\n"); + for (int strandId: isolatedStrandList) { + if (balTraces.containsKey(strandId)) { + balTraces.get(strandId).forEach(outputStr::append); + outputStr.append("\n"); + } } - outputStr.append("Non Isolated Workers:\n\n"); - for (int strandId: nonIsolatedWorkerList) { - balTraces.get(strandId).forEach(outputStr::append); - outputStr.append("\n"); + outputStr.append("Non Isolated Strands:\n\n"); + for (int strandId: nonIsolatedStrandList) { + if (balTraces.containsKey(strandId)) { + balTraces.get(strandId).forEach(outputStr::append); + outputStr.append("\n"); + } } return outputStr.toString(); } @@ -140,4 +146,4 @@ private static HotSpotDiagnosticMXBean getHotSpotDiagnosticMXBean() throws IOExc MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); return ManagementFactory.newPlatformMXBeanProxy(mBeanServer, HOT_SPOT_BEAN_NAME, HotSpotDiagnosticMXBean.class); } - } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ArrayValueImpl.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ArrayValueImpl.java index ad1896b75e76..8110bd8660a9 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ArrayValueImpl.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ArrayValueImpl.java @@ -305,7 +305,7 @@ public ArrayValueImpl(Type type, long size, BListInitialValueEntry[] initialValu /** * Get value in the given array index. - * + * * @param index array index * @return array value */ @@ -658,28 +658,36 @@ public void setArrayRefTypeForcefully(ArrayType type, int size) { } public void addInt(long index, long value) { + Type sourceType = TypeChecker.getType(value); if (intValues != null) { - prepareForAdd(index, value, PredefinedTypes.TYPE_INT, intValues.length); + if (sourceType == this.elementType) { + prepareForAddWithoutTypeCheck(index, intValues.length); + } else { + prepareForAdd(index, value, sourceType, intValues.length); + } intValues[(int) index] = value; return; } - - prepareForAdd(index, value, TypeChecker.getType(value), byteValues.length); + if (sourceType == this.elementType) { + prepareForAddWithoutTypeCheck(index, byteValues.length); + } else { + prepareForAdd(index, value, sourceType, byteValues.length); + } byteValues[(int) index] = (byte) ((Long) value).intValue(); } private void addBoolean(long index, boolean value) { - prepareForAdd(index, value, PredefinedTypes.TYPE_BOOLEAN, booleanValues.length); + prepareForAddWithoutTypeCheck(index, booleanValues.length); booleanValues[(int) index] = value; } private void addByte(long index, byte value) { - prepareForAdd(index, value, PredefinedTypes.TYPE_BYTE, byteValues.length); + prepareForAddWithoutTypeCheck(index, byteValues.length); byteValues[(int) index] = value; } private void addFloat(long index, double value) { - prepareForAdd(index, value, PredefinedTypes.TYPE_FLOAT, floatValues.length); + prepareForAddWithoutTypeCheck(index, floatValues.length); floatValues[(int) index] = value; } @@ -689,7 +697,12 @@ private void addString(long index, String value) { } private void addBString(long index, BString value) { - prepareForAdd(index, value, PredefinedTypes.TYPE_STRING, bStringValues.length); + Type sourceType = TypeChecker.getType(value); + if (sourceType == this.elementType) { + prepareForAddWithoutTypeCheck(index, bStringValues.length); + } else { + prepareForAdd(index, value, sourceType, bStringValues.length); + } bStringValues[(int) index] = value; } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/FPValue.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/FPValue.java index 35dd9a28be60..fd8f31aeaae8 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/FPValue.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/FPValue.java @@ -18,6 +18,7 @@ package io.ballerina.runtime.internal.values; import io.ballerina.runtime.api.Runtime; +import io.ballerina.runtime.api.concurrent.StrandMetadata; import io.ballerina.runtime.api.constants.RuntimeConstants; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.values.BFunctionPointer; @@ -40,20 +41,20 @@ public class FPValue implements BFunctionPointer, RefValue { final Type type; private BTypedesc typedesc; public Function function; - public boolean isIsolated; public String name; + public StrandMetadata metadata; public FPValue(Function function, Type type, String name, boolean isIsolated) { this.function = function; this.type = type; this.name = name; - this.isIsolated = isIsolated; + this.metadata = new StrandMetadata(isIsolated, null); } @Override public Object call(Runtime runtime, Object... t) { BalRuntime balRuntime = (BalRuntime) runtime; - return balRuntime.scheduler.callFP(this, null, t); + return balRuntime.scheduler.callFP(this, metadata, t); } @Override diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/methodgen/LambdaGen.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/methodgen/LambdaGen.java index d013ccdf566f..c20f64b0449d 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/methodgen/LambdaGen.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/bir/codegen/methodgen/LambdaGen.java @@ -363,6 +363,7 @@ private LambdaDetails populateAsyncLambdaDetails(BIRTerminator.AsyncCall asyncIn lambdaDetails.lhsType = asyncIns.lhsOp != null ? asyncIns.lhsOp.variableDcl.type : null; lambdaDetails.packageID = asyncIns.calleePkg; lambdaDetails.funcName = asyncIns.name.getValue(); + lambdaDetails.encodedFuncName = Utils.encodeFunctionIdentifier(lambdaDetails.funcName); if (!asyncIns.isVirtual) { populateLambdaFunctionDetails(lambdaDetails); } diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/desugar/ClosureGenerator.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/desugar/ClosureGenerator.java index 5d091d631269..dfe62e4a5eea 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/desugar/ClosureGenerator.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/desugar/ClosureGenerator.java @@ -36,6 +36,7 @@ import org.wso2.ballerinalang.compiler.semantics.model.symbols.BSymbol; import org.wso2.ballerinalang.compiler.semantics.model.symbols.BTypeSymbol; import org.wso2.ballerinalang.compiler.semantics.model.symbols.BVarSymbol; +import org.wso2.ballerinalang.compiler.semantics.model.symbols.SchedulerPolicy; import org.wso2.ballerinalang.compiler.semantics.model.symbols.SymTag; import org.wso2.ballerinalang.compiler.semantics.model.symbols.Symbols; import org.wso2.ballerinalang.compiler.semantics.model.types.BInvokableType; @@ -742,6 +743,7 @@ private BLangFunction createFunction(String funcName, Location pos, PackageID pk functionSymbol.kind = SymbolKind.FUNCTION; functionSymbol.retType = function.returnTypeNode.getBType(); functionSymbol.scope = new Scope(functionSymbol); + functionSymbol.schedulerPolicy = SchedulerPolicy.ANY; function.symbol = functionSymbol; return function; diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/TypeChecker.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/TypeChecker.java index cef5cdbdf1a3..44926d6ef6cf 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/TypeChecker.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/TypeChecker.java @@ -4085,17 +4085,28 @@ public void visit(BLangInvocation.BLangResourceAccessInvocation resourceAccessIn handleResourceAccessError(resourceAccessInvocation.resourceAccessPathSegments, resourceAccessInvocation.name.pos, DiagnosticErrorCode.UNDEFINED_RESOURCE_METHOD, data, resourceAccessInvocation.name, lhsExprType); + return; } else if (targetResourceFuncCount > 1) { - handleResourceAccessError(resourceAccessInvocation.resourceAccessPathSegments, resourceAccessInvocation.pos, - DiagnosticErrorCode.AMBIGUOUS_RESOURCE_ACCESS_NOT_YET_SUPPORTED, data, lhsExprType); - } else { - BResourceFunction targetResourceFunc = resourceFunctions.get(0); - checkExpr(resourceAccessInvocation.resourceAccessPathSegments, - getResourcePathType(targetResourceFunc.pathSegmentSymbols), data); - resourceAccessInvocation.symbol = targetResourceFunc.symbol; - resourceAccessInvocation.targetResourceFunc = targetResourceFunc; - checkResourceAccessParamAndReturnType(resourceAccessInvocation, targetResourceFunc, data); + //Filter the resource function with identifier segment + Optional first = resourceFunctions + .stream().filter(func -> func.pathSegmentSymbols.stream() + .allMatch(segment -> segment.kind == SymbolKind.RESOURCE_PATH_IDENTIFIER_SEGMENT)) + .findFirst(); + if (first.isPresent()) { + resourceFunctions = new ArrayList<>(List.of(first.get())); + } else { + handleResourceAccessError(resourceAccessInvocation.resourceAccessPathSegments, + resourceAccessInvocation.pos, DiagnosticErrorCode.AMBIGUOUS_RESOURCE_ACCESS_NOT_YET_SUPPORTED, + data, lhsExprType); + return; + } } + BResourceFunction targetResourceFunc = resourceFunctions.get(0); + checkExpr(resourceAccessInvocation.resourceAccessPathSegments, + getResourcePathType(targetResourceFunc.pathSegmentSymbols), data); + resourceAccessInvocation.symbol = targetResourceFunc.symbol; + resourceAccessInvocation.targetResourceFunc = targetResourceFunc; + checkResourceAccessParamAndReturnType(resourceAccessInvocation, targetResourceFunc, data); } private void handleResourceAccessError(BLangListConstructorExpr resourceAccessPathSegments, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 300d2997fa67..e7f90e8cbb64 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -86,15 +86,15 @@ mockitoCoreVersion="5.3.1" mockitoTestNGVersion="0.5.0" mustacheJavaCompilerVersion="0.8.9" mvel2Version="2.4.4.Final" -nettyBufferVersion="4.1.100.Final" -nettyCodecHttpVersion="4.1.45.Final" -nettyCodecHttp2Version="4.1.45.Final" -nettyCommonVersion="4.1.100.Final" -nettyHandlerVersion="4.1.45.Final" -nettyResolverVersion="4.1.100.Final" -nettyTransportNativeEpollVersion="4.1.100.Final" -nettyTransportNativeKqueueVersion="4.1.100.Final" -nettyTransportVersion="4.1.100.Final" +nettyBufferVersion="4.1.115.Final" +nettyCodecHttpVersion="4.1.115.Final" +nettyCodecHttp2Version="4.1.115.Final" +nettyCommonVersion="4.1.115.Final" +nettyHandlerVersion="4.1.115.Final" +nettyResolverVersion="4.1.115.Final" +nettyTransportNativeEpollVersion="4.1.115.Final" +nettyTransportNativeKqueueVersion="4.1.115.Final" +nettyTransportVersion="4.1.115.Final" okhttpVersion="3.14.0" openhftCompilerVersion="2.23ea0" openTelemetryApiVersion="1.32.0" diff --git a/langlib/lang.query/src/main/ballerina/types.bal b/langlib/lang.query/src/main/ballerina/types.bal index 980f0f8efd10..96ff7d11cbf4 100644 --- a/langlib/lang.query/src/main/ballerina/types.bal +++ b/langlib/lang.query/src/main/ballerina/types.bal @@ -680,9 +680,9 @@ class _GroupByFunction { } private function getKey(_Frame f) returns anydata|error { - anydata[] keys = []; + record{} keys = {}; foreach var key in self.keys { - keys.push( check f[key]); + keys[key] = check f[key]; } return keys; } diff --git a/misc/debug-adapter/modules/debug-adapter-core/spotbugs-exclude.xml b/misc/debug-adapter/modules/debug-adapter-core/spotbugs-exclude.xml index 834102614f91..7e528799f807 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/spotbugs-exclude.xml +++ b/misc/debug-adapter/modules/debug-adapter-core/spotbugs-exclude.xml @@ -31,13 +31,13 @@ - + - + diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BallerinaStackFrame.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BallerinaStackFrame.java index 93fdb18c6a86..981cb1940bdc 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BallerinaStackFrame.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BallerinaStackFrame.java @@ -35,8 +35,8 @@ import java.util.Map; import java.util.Optional; -import static org.ballerinalang.debugadapter.JBallerinaDebugServer.isBalStackFrame; import static org.ballerinalang.debugadapter.evaluation.utils.EvaluationUtils.STRAND_VAR_NAME; +import static org.ballerinalang.debugadapter.utils.ServerUtils.isBalStackFrame; import static org.ballerinalang.debugadapter.variable.VariableUtils.isService; import static org.ballerinalang.debugadapter.variable.VariableUtils.removeRedundantQuotes; import static org.wso2.ballerinalang.compiler.parser.BLangAnonymousModelHelper.LAMBDA; diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BreakpointProcessor.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BreakpointProcessor.java index 4ccb5c2e3c14..fea84fc9877e 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BreakpointProcessor.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BreakpointProcessor.java @@ -54,6 +54,7 @@ import java.util.concurrent.TimeoutException; import static org.ballerinalang.debugadapter.utils.PackageUtils.getQualifiedClassName; +import static org.ballerinalang.debugadapter.utils.ServerUtils.supportsBreakpointVerification; /** * Implementation of Ballerina breakpoint processor. The existing implementation is capable of processing advanced @@ -174,10 +175,7 @@ void restoreUserBreakpoints() { } context.getEventManager().deleteAllBreakpoints(); - for (Map.Entry> entry : userBreakpoints.entrySet()) { - String qClassName = entry.getKey(); - context.getDebuggeeVM().classesByName(qClassName).forEach(ref -> activateUserBreakPoints(ref, false)); - } + context.getDebuggeeVM().allClasses().forEach(ref -> activateUserBreakPoints(ref, false)); } /** @@ -202,7 +200,7 @@ void activateUserBreakPoints(ReferenceType referenceType, boolean shouldNotify) bpReq.enable(); // verifies the breakpoint reachability and notifies the client if required. - if (!breakpoint.isVerified()) { + if (supportsBreakpointVerification(context) && !breakpoint.isVerified()) { breakpoint.setVerified(true); if (shouldNotify) { notifyBreakPointChangesToClient(breakpoint); @@ -268,7 +266,8 @@ void activateDynamicBreakPoints(int threadId, DynamicBreakpointMode mode, boolea */ private boolean isWithinSameSource(Location currentLocation, Location prevLocation) { try { - return Objects.equals(currentLocation.sourcePath(), prevLocation.sourcePath()); + return Objects.equals(currentLocation.sourcePath(), prevLocation.sourcePath()) + && Objects.equals(currentLocation.method().name(), prevLocation.method().name()); } catch (AbsentInformationException e) { return false; } diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugExecutionManager.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugExecutionManager.java index 1f7079d1b9a5..c918e5fd186c 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugExecutionManager.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugExecutionManager.java @@ -43,6 +43,7 @@ public class DebugExecutionManager { private static final String SOCKET_CONNECTOR_NAME = "com.sun.jdi.SocketAttach"; private static final String CONNECTOR_ARGS_HOST = "hostname"; private static final String CONNECTOR_ARGS_PORT = "port"; + private static final String VALUE_UNKNOWN = "unknown"; private static final Logger LOGGER = LoggerFactory.getLogger(DebugExecutionManager.class); DebugExecutionManager(JBallerinaDebugServer server) { @@ -61,6 +62,12 @@ public Optional getPort() { return Optional.ofNullable(port); } + public String getRemoteVMAddress() { + String host = getHost().orElse(VALUE_UNKNOWN); + String port = getPort().map(String::valueOf).orElse(VALUE_UNKNOWN); + return String.format("%s:%s", host, port); + } + /** * Attaches to an existing JVM using an SocketAttachingConnector and returns the attached VM instance. */ diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugOutputLogger.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugOutputLogger.java index 10ca4cf19c9b..45c9345f269e 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugOutputLogger.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugOutputLogger.java @@ -36,6 +36,7 @@ public class DebugOutputLogger { public DebugOutputLogger(IDebugProtocolClient client) { this.client = client; + this.isCompilationDone = false; } /** @@ -129,4 +130,8 @@ private static boolean isInternalLog(String output) { || output.startsWith("JAVACMD") || output.startsWith("Stream closed"); } + + public void reset() { + isCompilationDone = false; + } } diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugProjectCache.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugProjectCache.java index 62421f940571..1de45780d3db 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugProjectCache.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugProjectCache.java @@ -52,32 +52,24 @@ public DebugProjectCache() { public Project getProject(Path filePath) { Map.Entry projectKindAndRoot = computeProjectKindAndRoot(filePath); Path projectRoot = projectKindAndRoot.getValue(); - if (!loadedProjects.containsKey(projectRoot)) { - addProject(loadProject(filePath.toAbsolutePath().toString())); - } - return loadedProjects.get(projectRoot); + + return loadedProjects.computeIfAbsent(projectRoot, key -> loadProject(projectKindAndRoot)); } /** - * Adds the given project instance into the cache. - * - * @param project project instance. + * Clears the project cache. */ - public void addProject(Project project) { - Path projectSourceRoot = project.sourceRoot().toAbsolutePath(); - loadedProjects.put(projectSourceRoot, project); + public void clear() { + loadedProjects.clear(); } /** * Loads the target ballerina source project instance using the Project API, from the file path of the open/active * editor instance in the client(plugin) side. - * - * @param filePath file path of the open/active editor instance in the plugin side. */ - private static Project loadProject(String filePath) { - Map.Entry projectKindAndProjectRootPair = computeProjectKindAndRoot(Path.of(filePath)); - ProjectKind projectKind = projectKindAndProjectRootPair.getKey(); - Path projectRoot = projectKindAndProjectRootPair.getValue(); + private static Project loadProject(Map.Entry projectKindAndRoot) { + ProjectKind projectKind = projectKindAndRoot.getKey(); + Path projectRoot = projectKindAndRoot.getValue(); BuildOptions options = BuildOptions.builder().setOffline(true).build(); if (projectKind == ProjectKind.BUILD_PROJECT) { return BuildProject.load(projectRoot, options); diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/ExecutionContext.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/ExecutionContext.java index 04d26d2870f7..dee5fe7fa5ce 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/ExecutionContext.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/ExecutionContext.java @@ -144,17 +144,12 @@ public Project getSourceProject() { public void setSourceProject(Project sourceProject) { this.sourceProject = sourceProject; this.setSourceProjectRoot(sourceProject.sourceRoot().toAbsolutePath().toString()); - updateProjectCache(sourceProject); } public DebugProjectCache getProjectCache() { return projectCache; } - public void updateProjectCache(Project project) { - this.projectCache.addProject(project); - } - public String getSourceProjectRoot() { return sourceProjectRoot; } @@ -171,6 +166,19 @@ public boolean getSupportsRunInTerminalRequest() { return supportsRunInTerminalRequest; } + public void reset() { + this.projectCache.clear(); + this.debugMode = null; + this.debuggeeVM = null; + this.prevLocation = null; + this.sourceProject = null; + this.launchedProcess = null; + this.sourceProjectRoot = null; + this.terminateRequestReceived = false; + this.supportsRunInTerminalRequest = false; + this.prevInstruction = DebugInstruction.CONTINUE; + } + /** * Currently supported debug configuration modes. */ diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java index 45150c779484..5f2503c3e3e1 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java @@ -51,6 +51,7 @@ import org.ballerinalang.debugadapter.runner.BProgramRunner; import org.ballerinalang.debugadapter.runner.BSingleFileRunner; import org.ballerinalang.debugadapter.utils.PackageUtils; +import org.ballerinalang.debugadapter.utils.ServerUtils; import org.ballerinalang.debugadapter.variable.BCompoundVariable; import org.ballerinalang.debugadapter.variable.BSimpleVariable; import org.ballerinalang.debugadapter.variable.BVariable; @@ -74,6 +75,7 @@ import org.eclipse.lsp4j.debug.InitializeRequestArguments; import org.eclipse.lsp4j.debug.NextArguments; import org.eclipse.lsp4j.debug.PauseArguments; +import org.eclipse.lsp4j.debug.RestartArguments; import org.eclipse.lsp4j.debug.RunInTerminalRequestArguments; import org.eclipse.lsp4j.debug.RunInTerminalResponse; import org.eclipse.lsp4j.debug.Scope; @@ -82,7 +84,6 @@ import org.eclipse.lsp4j.debug.SetBreakpointsArguments; import org.eclipse.lsp4j.debug.SetBreakpointsResponse; import org.eclipse.lsp4j.debug.SetExceptionBreakpointsArguments; -import org.eclipse.lsp4j.debug.Source; import org.eclipse.lsp4j.debug.SourceArguments; import org.eclipse.lsp4j.debug.SourceBreakpoint; import org.eclipse.lsp4j.debug.SourceResponse; @@ -131,10 +132,13 @@ import static org.ballerinalang.debugadapter.completion.util.CompletionUtil.getTriggerCharacters; import static org.ballerinalang.debugadapter.completion.util.CompletionUtil.getVisibleSymbolCompletions; import static org.ballerinalang.debugadapter.completion.util.CompletionUtil.triggerCharactersFound; -import static org.ballerinalang.debugadapter.utils.PackageUtils.BAL_FILE_EXT; import static org.ballerinalang.debugadapter.utils.PackageUtils.GENERATED_VAR_PREFIX; import static org.ballerinalang.debugadapter.utils.PackageUtils.INIT_CLASS_NAME; import static org.ballerinalang.debugadapter.utils.PackageUtils.getQualifiedClassName; +import static org.ballerinalang.debugadapter.utils.ServerUtils.isBalStackFrame; +import static org.ballerinalang.debugadapter.utils.ServerUtils.isBalStrand; +import static org.ballerinalang.debugadapter.utils.ServerUtils.isNoDebugMode; +import static org.ballerinalang.debugadapter.utils.ServerUtils.toBalBreakpoint; /** * JBallerina debug server implementation. @@ -146,12 +150,12 @@ public class JBallerinaDebugServer implements IDebugProtocolServer { private DebugExecutionManager executionManager; private JDIEventProcessor eventProcessor; private final ExecutionContext context; - private ThreadReferenceProxyImpl activeThread; private SuspendedContext suspendedContext; private DebugOutputLogger outputLogger; private DebugExpressionEvaluator evaluator; + private ThreadReferenceProxyImpl activeThread; - private final AtomicInteger nextVarReference = new AtomicInteger(); + private final AtomicInteger nextVarReference = new AtomicInteger(1); private final Map stackFrames = new HashMap<>(); private final Map threadStackTraces = new HashMap<>(); private final Map scopeIdToFrameIds = new HashMap<>(); @@ -177,7 +181,7 @@ public ExecutionContext getContext() { return context; } - ClientConfigHolder getClientConfigHolder() { + public ClientConfigHolder getClientConfigHolder() { return clientConfigHolder; } @@ -196,8 +200,7 @@ public CompletableFuture initialize(InitializeRequestArguments arg capabilities.setSupportsLogPoints(true); capabilities.setSupportsCompletionsRequest(true); capabilities.setCompletionTriggerCharacters(getTriggerCharacters().toArray(String[]::new)); - // Todo - Implement - capabilities.setSupportsRestartRequest(false); + capabilities.setSupportsRestartRequest(true); // unsupported capabilities capabilities.setSupportsHitConditionalBreakpoints(false); capabilities.setSupportsModulesRequest(false); @@ -221,12 +224,13 @@ public CompletableFuture initialize(InitializeRequestArguments arg public CompletableFuture setBreakpoints(SetBreakpointsArguments args) { return CompletableFuture.supplyAsync(() -> { SetBreakpointsResponse bpResponse = new SetBreakpointsResponse(); - if (isNoDebugMode()) { + if (isNoDebugMode(context)) { return bpResponse; } BalBreakpoint[] balBreakpoints = Arrays.stream(args.getBreakpoints()) - .map((SourceBreakpoint sourceBreakpoint) -> toBalBreakpoint(sourceBreakpoint, args.getSource())) + .map((SourceBreakpoint sourceBreakpoint) -> toBalBreakpoint(context, sourceBreakpoint, + args.getSource())) .toArray(BalBreakpoint[]::new); LinkedHashMap breakpointsMap = new LinkedHashMap<>(); @@ -265,35 +269,20 @@ public CompletableFuture configurationDone(ConfigurationDoneArguments args @Override public CompletableFuture launch(Map args) { try { - clearState(); - context.setDebugMode(ExecutionContext.DebugMode.LAUNCH); clientConfigHolder = new ClientLaunchConfigHolder(args); - Project sourceProject = context.getProjectCache().getProject(Path.of(clientConfigHolder.getSourcePath())); - context.setSourceProject(sourceProject); - String sourceProjectRoot = context.getSourceProjectRoot(); - BProgramRunner programRunner = context.getSourceProject() instanceof SingleFileProject ? - new BSingleFileRunner((ClientLaunchConfigHolder) clientConfigHolder, sourceProjectRoot) : - new BPackageRunner((ClientLaunchConfigHolder) clientConfigHolder, sourceProjectRoot); - - if (context.getSupportsRunInTerminalRequest() && clientConfigHolder.getRunInTerminalKind() != null) { - launchInTerminal(programRunner); - } else { - context.setLaunchedProcess(programRunner.start()); - startListeningToProgramOutput(); - } + launchDebuggeeProgram(); return CompletableFuture.completedFuture(null); } catch (Exception e) { - outputLogger.sendErrorOutput("Failed to launch the ballerina program due to: " + e); - return CompletableFuture.completedFuture(null); + outputLogger.sendErrorOutput("Failed to launch the Ballerina program due to: " + e.getMessage()); + return CompletableFuture.failedFuture(e); } } @Override public CompletableFuture attach(Map args) { try { - clearState(); - context.setDebugMode(ExecutionContext.DebugMode.ATTACH); clientConfigHolder = new ClientAttachConfigHolder(args); + context.setDebugMode(ExecutionContext.DebugMode.ATTACH); Project sourceProject = context.getProjectCache().getProject(Path.of(clientConfigHolder.getSourcePath())); context.setSourceProject(sourceProject); ClientAttachConfigHolder configHolder = (ClientAttachConfigHolder) clientConfigHolder; @@ -301,6 +290,7 @@ public CompletableFuture attach(Map args) { String hostName = configHolder.getHostName().orElse(""); int portName = configHolder.getDebuggePort(); attachToRemoteVM(hostName, portName); + return CompletableFuture.completedFuture(null); } catch (Exception e) { String host = ((ClientAttachConfigHolder) clientConfigHolder).getHostName().orElse(LOCAL_HOST); String portName; @@ -310,11 +300,11 @@ public CompletableFuture attach(Map args) { portName = VALUE_UNKNOWN; } LOGGER.error(e.getMessage()); - outputLogger.sendErrorOutput(String.format("Failed to attach to the target VM, address: '%s:%s'.", - host, portName)); - terminateDebugServer(context.getDebuggeeVM() != null, false); + outputLogger.sendErrorOutput(String.format("Failed to attach to the target VM address: '%s:%s' due to: %s", + host, portName, e.getMessage())); + terminateDebugSession(context.getDebuggeeVM() != null, false); + return CompletableFuture.failedFuture(e); } - return CompletableFuture.completedFuture(null); } @Override @@ -359,7 +349,7 @@ public CompletableFuture stackTrace(StackTraceArguments args } else { StackFrame[] validFrames = activeThread.frames().stream() .map(this::toDapStackFrame) - .filter(JBallerinaDebugServer::isValidFrame) + .filter(ServerUtils::isValidFrame) .toArray(StackFrame[]::new); stackTraceResponse.setStackFrames(validFrames); threadStackTraces.put(activeThread.uniqueID(), validFrames); @@ -458,6 +448,41 @@ public CompletableFuture stepOut(StepOutArguments args) { return CompletableFuture.completedFuture(null); } + @Override + public CompletableFuture restart(RestartArguments args) { + if (context.getDebugMode() == ExecutionContext.DebugMode.ATTACH) { + outputLogger.sendErrorOutput("Restart is not supported in remote debug mode."); + return CompletableFuture.completedFuture(null); + } + + try { + resetServer(); + launchDebuggeeProgram(); + return CompletableFuture.completedFuture(null); + } catch (Exception e) { + LOGGER.error("Failed to restart the ballerina program due to: " + e.getMessage(), e); + outputLogger.sendErrorOutput("Failed to restart the ballerina program"); + return CompletableFuture.completedFuture(null); + } + } + + private void launchDebuggeeProgram() throws Exception { + context.setDebugMode(ExecutionContext.DebugMode.LAUNCH); + Project sourceProject = context.getProjectCache().getProject(Path.of(clientConfigHolder.getSourcePath())); + context.setSourceProject(sourceProject); + String sourceProjectRoot = context.getSourceProjectRoot(); + BProgramRunner programRunner = context.getSourceProject() instanceof SingleFileProject ? + new BSingleFileRunner((ClientLaunchConfigHolder) clientConfigHolder, sourceProjectRoot) : + new BPackageRunner((ClientLaunchConfigHolder) clientConfigHolder, sourceProjectRoot); + + if (context.getSupportsRunInTerminalRequest() && clientConfigHolder.getRunInTerminalKind() != null) { + launchInTerminal(programRunner); + } else { + context.setLaunchedProcess(programRunner.start()); + startListeningToProgramOutput(); + } + } + @Override public CompletableFuture setExceptionBreakpoints(SetExceptionBreakpointsArguments args) { return CompletableFuture.completedFuture(null); @@ -569,14 +594,14 @@ public CompletableFuture completions(CompletionsArguments a public CompletableFuture disconnect(DisconnectArguments args) { context.setTerminateRequestReceived(true); boolean terminateDebuggee = Objects.requireNonNullElse(args.getTerminateDebuggee(), true); - terminateDebugServer(terminateDebuggee, true); + terminateDebugSession(terminateDebuggee, true); return CompletableFuture.completedFuture(null); } @Override public CompletableFuture terminate(TerminateArguments args) { context.setTerminateRequestReceived(true); - terminateDebugServer(true, true); + terminateDebugSession(true, true); return CompletableFuture.completedFuture(null); } @@ -602,7 +627,7 @@ public CompletableFuture runInTerminal(RunInTerminalReque if (context.getDebuggeeVM() == null) { // shut down debug server outputLogger.sendErrorOutput("Failed to attach to the target VM"); - terminateDebugServer(false, true); + terminateDebugSession(false, true); // shut down client terminal int shellProcessId = ((RunInTerminalResponse) response).getShellProcessId(); @@ -640,25 +665,17 @@ private void launchInTerminal(BProgramRunner programRunner) throws ClientConfigu /** * Terminates the debug server. * - * @param terminateDebuggee indicates whether the remote VM should also be terminated - * @param logsEnabled indicates whether the debug server logs should be sent to the client + * @param shouldTerminateDebuggee indicates whether the remote VM should also be terminated + * @param enableClientLogs indicates whether the debug server logs should be sent to the client */ - void terminateDebugServer(boolean terminateDebuggee, boolean logsEnabled) { + void terminateDebugSession(boolean shouldTerminateDebuggee, boolean enableClientLogs) { // Destroys launched process, if presents. if (context.getLaunchedProcess().isPresent() && context.getLaunchedProcess().get().isAlive()) { killProcessWithDescendants(context.getLaunchedProcess().get()); } - // Destroys remote VM process, if `terminateDebuggee' flag is set. - if (terminateDebuggee && context.getDebuggeeVM() != null) { - int exitCode = 0; - if (context.getDebuggeeVM().process() != null) { - exitCode = killProcessWithDescendants(context.getDebuggeeVM().process()); - } - try { - context.getDebuggeeVM().exit(exitCode); - } catch (Exception ignored) { - // It is okay to ignore the VM exit Exceptions, in-case the remote debuggee is already terminated. - } + // Destroys remote VM process, if 'shouldTerminateDebuggee' flag is set. + if (shouldTerminateDebuggee) { + terminateDebuggee(); } // If 'terminationRequestReceived' is false, debug server termination should have been triggered from the @@ -670,11 +687,9 @@ void terminateDebugServer(boolean terminateDebuggee, boolean logsEnabled) { } // Notifies user. - if (executionManager != null && logsEnabled) { - String address = (executionManager.getHost().isPresent() && executionManager.getPort().isPresent()) ? - executionManager.getHost().get() + ":" + executionManager.getPort().get() : VALUE_UNKNOWN; + if (executionManager != null && enableClientLogs) { outputLogger.sendDebugServerOutput(String.format(System.lineSeparator() + "Disconnected from the target " + - "VM, address: '%s'", address)); + "VM, address: '%s'", executionManager.getRemoteVMAddress())); } // Exits from the debug server VM. @@ -684,6 +699,25 @@ void terminateDebugServer(boolean terminateDebuggee, boolean logsEnabled) { }).start(); } + /** + * Terminates the debuggee VM. + */ + private void terminateDebuggee() { + if (Objects.isNull(context.getDebuggeeVM())) { + return; + } + + int exitCode = 0; + if (context.getDebuggeeVM().process() != null) { + exitCode = killProcessWithDescendants(context.getDebuggeeVM().process()); + } + try { + context.getDebuggeeVM().exit(exitCode); + } catch (Exception ignored) { + // It is okay to ignore the VM exit Exceptions, in-case the remote debuggee is already terminated. + } + } + private static int killProcessWithDescendants(Process parent) { try { // Kills the descendants of the process. The descendants of a process are the children @@ -755,13 +789,6 @@ private StackFrame toDapStackFrame(StackFrameProxyImpl stackFrameProxy) { } } - private BalBreakpoint toBalBreakpoint(SourceBreakpoint sourceBreakpoint, Source source) { - BalBreakpoint breakpoint = new BalBreakpoint(source, sourceBreakpoint.getLine()); - breakpoint.setCondition(sourceBreakpoint.getCondition()); - breakpoint.setLogMessage(sourceBreakpoint.getLogMessage()); - return breakpoint; - } - /** * Returns a map of all currently running threads in the remote VM, against their unique ID. *

@@ -781,7 +808,7 @@ Map getAllThreads() { threadsMap.put((int) threadReference.uniqueID(), new ThreadReferenceProxyImpl(context.getDebuggeeVM(), threadReference)); } - for (ThreadReference threadReference: eventProcessor.getVirtualThreads()) { + for (ThreadReference threadReference : eventProcessor.getVirtualThreads()) { threadsMap.put((int) threadReference.uniqueID(), new ThreadReferenceProxyImpl(context.getDebuggeeVM(), threadReference)); } @@ -814,47 +841,6 @@ && isBalStrand(threadReference) return balStrandThreads; } - /** - * Validates whether the given DAP thread reference represents a ballerina strand. - *

- * - * @param threadReference DAP thread reference - * @return true if the given DAP thread reference represents a ballerina strand. - */ - private static boolean isBalStrand(ThreadReference threadReference) { - // Todo - Refactor to use thread proxy implementation - try { - return isBalStackFrame(threadReference.frames().get(0)); - } catch (Exception e) { - return false; - } - } - - /** - * Validates whether the given DAP stack frame represents a ballerina call stack frame. - * - * @param frame DAP stack frame - * @return true if the given DAP stack frame represents a ballerina call stack frame. - */ - static boolean isBalStackFrame(com.sun.jdi.StackFrame frame) { - // Todo - Refactor to use stack frame proxy implementation - try { - return frame.location().sourceName().endsWith(BAL_FILE_EXT); - } catch (Exception e) { - return false; - } - } - - /** - * Validates a given ballerina stack frame for its source information. - * - * @param stackFrame ballerina stack frame - * @return true if its a valid ballerina frame - */ - static boolean isValidFrame(StackFrame stackFrame) { - return stackFrame != null && stackFrame.getSource() != null && stackFrame.getLine() > 0; - } - /** * Asynchronously listens to remote debuggee stdout + error streams and redirects the output to the client debug * console. @@ -872,7 +858,7 @@ private void startListeningToProgramOutput() { // the STDOUT stream. outputLogger.sendConsoleOutput(line); if (context.getDebuggeeVM() == null && line.contains(COMPILATION_ERROR_MESSAGE)) { - terminateDebugServer(false, true); + terminateDebugSession(false, true); } } } catch (IOException ignored) { @@ -892,7 +878,7 @@ private void startListeningToProgramOutput() { if (line.contains("Listening for transport dt_socket")) { attachToRemoteVM("", clientConfigHolder.getDebuggePort()); } else if (context.getDebuggeeVM() == null && line.contains(COMPILATION_ERROR_MESSAGE)) { - terminateDebugServer(false, true); + terminateDebugSession(false, true); } outputLogger.sendProgramOutput(line); } @@ -908,7 +894,7 @@ private void startListeningToProgramOutput() { LOGGER.error(e.getMessage()); outputLogger.sendDebugServerOutput(String.format("Failed to attach to the target VM, address: '%s:%s'.", host, portName)); - terminateDebugServer(context.getDebuggeeVM() != null, true); + terminateDebugSession(context.getDebuggeeVM() != null, true); } }); } @@ -928,14 +914,14 @@ private void attachToRemoteVM(String hostName, int portName) throws IOException, erm.createClassPrepareRequest().enable(); erm.createThreadStartRequest().enable(); erm.createThreadDeathRequest().enable(); - eventProcessor.listenAsync(); + eventProcessor.startListenAsync(); } /** * Clears previous state information and prepares for the given debug instruction type execution. */ private void prepareFor(DebugInstruction instruction, int threadId) { - clearState(); + clearSuspendedState(); BreakpointProcessor bpProcessor = eventProcessor.getBreakpointProcessor(); DebugInstruction prevInstruction = context.getPrevInstruction(); if (prevInstruction == DebugInstruction.STEP_OVER && instruction == DebugInstruction.CONTINUE) { @@ -948,11 +934,6 @@ private void prepareFor(DebugInstruction instruction, int threadId) { context.setPrevInstruction(instruction); } - private boolean isNoDebugMode() { - ClientConfigHolder confHolder = context.getAdapter().getClientConfigHolder(); - return confHolder instanceof ClientLaunchConfigHolder launchConfigHolder && launchConfigHolder.isNoDebugMode(); - } - private Variable[] computeGlobalScopeVariables(VariablesArguments requestArgs) { int stackFrameReference = requestArgs.getVariablesReference(); String classQName = PackageUtils.getQualifiedClassName(suspendedContext, INIT_CLASS_NAME); @@ -1187,9 +1168,9 @@ private EvaluateResponse constructEvaluateResponse(EvaluateArguments args, BVari } /** - * Clears state information. + * Clears the suspended state information. */ - private void clearState() { + private void clearSuspendedState() { suspendedContext = null; evaluator = null; activeThread = null; @@ -1200,4 +1181,15 @@ private void clearState() { threadStackTraces.clear(); nextVarReference.set(1); } + + /** + * Clears all state information. + */ + private void resetServer() { + Optional.ofNullable(eventProcessor).ifPresent(JDIEventProcessor::reset); + Optional.ofNullable(outputLogger).ifPresent(DebugOutputLogger::reset); + terminateDebuggee(); + clearSuspendedState(); + context.reset(); + } } diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java index a5e4e9f3ef09..1d49eb3edcf2 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java @@ -35,6 +35,7 @@ import org.ballerinalang.debugadapter.breakpoint.BalBreakpoint; import org.ballerinalang.debugadapter.jdi.StackFrameProxyImpl; import org.ballerinalang.debugadapter.jdi.ThreadReferenceProxyImpl; +import org.ballerinalang.debugadapter.utils.ServerUtils; import org.eclipse.lsp4j.debug.ContinuedEventArguments; import org.eclipse.lsp4j.debug.StoppedEventArguments; import org.eclipse.lsp4j.debug.StoppedEventArgumentsReason; @@ -44,11 +45,12 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; -import static org.ballerinalang.debugadapter.JBallerinaDebugServer.isBalStackFrame; import static org.ballerinalang.debugadapter.utils.PackageUtils.BAL_FILE_EXT; +import static org.ballerinalang.debugadapter.utils.ServerUtils.isBalStackFrame; /** * JDI Event processor implementation. @@ -57,24 +59,26 @@ public class JDIEventProcessor { private final ExecutionContext context; private final BreakpointProcessor breakpointProcessor; - private boolean isRemoteVmAttached = false; + private volatile boolean isRemoteVmAttached; + private volatile boolean interruptFlag; private final List stepRequests = new CopyOnWriteArrayList<>(); private static final List virtualThreads = new CopyOnWriteArrayList<>(); private static final Logger LOGGER = LoggerFactory.getLogger(JDIEventProcessor.class); + private CompletableFuture listeningTask; + JDIEventProcessor(ExecutionContext context) { this.context = context; - breakpointProcessor = new BreakpointProcessor(context, this); + this.breakpointProcessor = new BreakpointProcessor(context, this); + this.isRemoteVmAttached = true; + this.interruptFlag = false; + this.listeningTask = null; } BreakpointProcessor getBreakpointProcessor() { return breakpointProcessor; } - List getStepRequests() { - return stepRequests; - } - List getVirtualThreads() { return virtualThreads; } @@ -82,30 +86,52 @@ List getVirtualThreads() { /** * Asynchronously listens and processes the incoming JDI events. */ - void listenAsync() { - CompletableFuture.runAsync(() -> { - isRemoteVmAttached = true; - while (isRemoteVmAttached) { + void startListenAsync() { + // Store the future for potential cancellation + listeningTask = CompletableFuture.runAsync(() -> { + while (isRemoteVmAttached && !interruptFlag) { try { EventSet eventSet = context.getDebuggeeVM().eventQueue().remove(); EventIterator eventIterator = eventSet.eventIterator(); - while (eventIterator.hasNext() && isRemoteVmAttached) { + while (eventIterator.hasNext() && isRemoteVmAttached && !interruptFlag) { processEvent(eventSet, eventIterator.next()); } } catch (Exception e) { - LOGGER.error(e.getMessage(), e); + LOGGER.error("Error occurred while processing JDI events.", e); } } - // Tries terminating the debug server, only if there is no any termination requests received from the - // debug client. - if (!context.isTerminateRequestReceived()) { - // It is not required to terminate the debuggee (remote VM) in here, since it must be disconnected or - // dead by now. - context.getAdapter().terminateDebugServer(false, true); - } + + cleanupAfterListening(); }); } + /** + * Stops the async listening process. + * + * @param force if true, immediately stops listening; + * if false, allows current event processing to complete + */ + private void stopListening(boolean force) { + interruptFlag = true; + if (Objects.nonNull(listeningTask) && !listeningTask.isDone() && force) { + // Attempt to interrupt the task if possible + listeningTask.cancel(true); + } + } + + /** + * Performs cleanup after listening stops. + */ + private void cleanupAfterListening() { + if (!context.isTerminateRequestReceived() && !interruptFlag) { + // It is not required to terminate the debuggee (remote VM) at this point, since it must be disconnected or + // dead by now. + context.getAdapter().terminateDebugSession(false, true); + } + isRemoteVmAttached = true; + interruptFlag = false; + } + private void processEvent(EventSet eventSet, Event event) { if (event instanceof ClassPrepareEvent evt) { if (context.getPrevInstruction() != DebugInstruction.STEP_OVER) { @@ -149,8 +175,7 @@ void enableBreakpoints(String qClassName, LinkedHashMap // Setting breakpoints to an already running debug session. context.getEventManager().deleteAllBreakpoints(); - context.getDebuggeeVM().classesByName(qClassName) - .forEach(ref -> breakpointProcessor.activateUserBreakPoints(ref, false)); + context.getDebuggeeVM().allClasses().forEach(ref -> breakpointProcessor.activateUserBreakPoints(ref, false)); } void sendStepRequest(int threadId, int stepType) { @@ -201,7 +226,7 @@ List filterValidBallerinaFrames(List j if (balStackFrame.getAsDAPStackFrame().isEmpty()) { continue; } - if (JBallerinaDebugServer.isValidFrame(balStackFrame.getAsDAPStackFrame().get())) { + if (ServerUtils.isValidFrame(balStackFrame.getAsDAPStackFrame().get())) { validFrames.add(balStackFrame); } } catch (Exception ignored) { @@ -253,4 +278,10 @@ void notifyStopEvent(String reason, long threadId) { stoppedEventArguments.setAllThreadsStopped(true); context.getClient().stopped(stoppedEventArguments); } + + public void reset() { + stopListening(true); + stepRequests.clear(); + virtualThreads.clear(); + } } diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/breakpoint/BalBreakpoint.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/breakpoint/BalBreakpoint.java index d12a65ebc3ce..b3e4da50e020 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/breakpoint/BalBreakpoint.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/breakpoint/BalBreakpoint.java @@ -40,6 +40,7 @@ public class BalBreakpoint { private String condition; private LogMessage logMessage; private boolean isVerified; + private boolean supportsVerification; private static final AtomicInteger nextID = new AtomicInteger(0); @@ -48,6 +49,7 @@ public BalBreakpoint(Source source, int line) { this.source = source; this.line = line; this.isVerified = false; + this.supportsVerification = false; } public Integer getLine() { @@ -91,11 +93,15 @@ public Breakpoint getAsDAPBreakpoint() { breakpoint.setId(id); breakpoint.setLine(line); breakpoint.setSource(source); - breakpoint.setVerified(isVerified); + breakpoint.setVerified(!supportsVerification || isVerified); return breakpoint; } private boolean isTemplate(String logMessage) { return logMessage != null && Pattern.compile(INTERPOLATION_REGEX).matcher(logMessage).find(); } + + public void setSupportsVerification(boolean supportsVerification) { + this.supportsVerification = supportsVerification; + } } diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/config/ClientConfigHolder.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/config/ClientConfigHolder.java index ebe94c4d886b..c2d294585858 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/config/ClientConfigHolder.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/config/ClientConfigHolder.java @@ -40,6 +40,7 @@ public class ClientConfigHolder { protected static final String ARG_DEBUGGEE_PORT = "debuggeePort"; private static final String ARG_CAPABILITIES = "capabilities"; private static final String ARG_SUPPORT_READONLY_EDITOR = "supportsReadOnlyEditors"; + private static final String ARG_SUPPORT_BP_VERIFICATION = "supportsBreakpointVerification"; private static final String ARG_TERMINAL_KIND = "terminal"; private static final String INTEGRATED_TERMINAL_KIND = "INTEGRATED"; private static final String EXTERNAL_TERMINAL_KIND = "EXTERNAL"; @@ -88,6 +89,15 @@ public Optional getExtendedCapabilities() { extendedClientCapabilities.setSupportsReadOnlyEditors(false); } + Object bpVerificationConfig = capabilities.get(ARG_SUPPORT_BP_VERIFICATION); + if (bpVerificationConfig instanceof Boolean b) { + extendedClientCapabilities.setSupportsBreakpointVerification(b); + } else if (bpVerificationConfig instanceof String s) { + extendedClientCapabilities.setSupportsBreakpointVerification(Boolean.parseBoolean(s)); + } else { + extendedClientCapabilities.setSupportsBreakpointVerification(false); + } + return Optional.ofNullable(extendedClientCapabilities); } @@ -122,6 +132,7 @@ public enum ClientConfigKind { public static class ExtendedClientCapabilities { private boolean supportsReadOnlyEditors = false; + private boolean supportsBreakpointVerification = false; public boolean supportsReadOnlyEditors() { return supportsReadOnlyEditors; @@ -130,5 +141,13 @@ public boolean supportsReadOnlyEditors() { public void setSupportsReadOnlyEditors(boolean supportsReadOnlyEditors) { this.supportsReadOnlyEditors = supportsReadOnlyEditors; } + + public boolean supportsBreakpointVerification() { + return supportsBreakpointVerification; + } + + public void setSupportsBreakpointVerification(boolean supportsBreakpointVerification) { + this.supportsBreakpointVerification = supportsBreakpointVerification; + } } } diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/ServerUtils.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/ServerUtils.java new file mode 100644 index 000000000000..6efda07cec62 --- /dev/null +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/ServerUtils.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://wso2.com). + * + * 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 org.ballerinalang.debugadapter.utils; + +import com.sun.jdi.ThreadReference; +import org.ballerinalang.debugadapter.ExecutionContext; +import org.ballerinalang.debugadapter.breakpoint.BalBreakpoint; +import org.ballerinalang.debugadapter.config.ClientConfigHolder; +import org.ballerinalang.debugadapter.config.ClientLaunchConfigHolder; +import org.eclipse.lsp4j.debug.Source; +import org.eclipse.lsp4j.debug.SourceBreakpoint; +import org.eclipse.lsp4j.debug.StackFrame; + +import java.util.Objects; + +import static org.ballerinalang.debugadapter.utils.PackageUtils.BAL_FILE_EXT; + +/** + * Ballerina debug server related utility functions. + * + * @since 2201.11.0 + */ +public class ServerUtils { + + /** + * Checks whether the debug server should run in no-debug mode. + * + * @param context debug context + * @return true if the debug mode is no-debug mode + */ + public static boolean isNoDebugMode(ExecutionContext context) { + ClientConfigHolder confHolder = context.getAdapter().getClientConfigHolder(); + return confHolder instanceof ClientLaunchConfigHolder launchConfigHolder && launchConfigHolder.isNoDebugMode(); + } + + /** + * Validates whether the given DAP thread reference represents a ballerina strand. + * + * @param threadReference DAP thread reference + * @return true if the given DAP thread reference represents a ballerina strand + */ + public static boolean isBalStrand(ThreadReference threadReference) { + // Todo - Refactor to use thread proxy implementation + try { + return isBalStackFrame(threadReference.frames().getFirst()); + } catch (Exception e) { + return false; + } + } + + /** + * Validates whether the given DAP stack frame represents a ballerina call stack frame. + * + * @param frame DAP stack frame + * @return true if the given DAP stack frame represents a ballerina call stack frame + */ + public static boolean isBalStackFrame(com.sun.jdi.StackFrame frame) { + // Todo - Refactor to use stack frame proxy implementation + try { + return frame.location().sourceName().endsWith(BAL_FILE_EXT); + } catch (Exception e) { + return false; + } + } + + /** + * Validates a given ballerina stack frame for its source information. + * + * @param stackFrame ballerina stack frame + * @return true if it's a valid ballerina frame + */ + public static boolean isValidFrame(StackFrame stackFrame) { + return stackFrame != null && stackFrame.getSource() != null && stackFrame.getLine() > 0; + } + + /** + * Converts a given DAP source breakpoint instance to a ballerina breakpoint instance. + * + * @param context debug context + * @param sourceBp source breakpoint + * @param source source + * @return ballerina breakpoint + */ + public static BalBreakpoint toBalBreakpoint(ExecutionContext context, SourceBreakpoint sourceBp, Source source) { + BalBreakpoint breakpoint = new BalBreakpoint(source, sourceBp.getLine()); + breakpoint.setCondition(sourceBp.getCondition()); + breakpoint.setLogMessage(sourceBp.getLogMessage()); + // If the debug client doesn't support breakpoint verification, mark the breakpoint as verified by default. + if (supportsBreakpointVerification(context)) { + breakpoint.setSupportsVerification(true); + } + + return breakpoint; + } + + /** + * Checks whether the connected debug client supports breakpoint verification. + * + * @param context debug context + * @return true if the connected debug client supports breakpoint verification + */ + public static boolean supportsBreakpointVerification(ExecutionContext context) { + ClientConfigHolder configHolder = context.getAdapter().getClientConfigHolder(); + + return Objects.nonNull(configHolder) && configHolder.getExtendedCapabilities() + .map(ClientConfigHolder.ExtendedClientCapabilities::supportsBreakpointVerification) + .orElse(false); + } +} diff --git a/tests/ballerina-compiler-api-test/src/test/java/io/ballerina/semantic/api/test/actions/SymbolsInResourceAccessActionTest.java b/tests/ballerina-compiler-api-test/src/test/java/io/ballerina/semantic/api/test/actions/SymbolsInResourceAccessActionTest.java index 0916a5d733f7..d75f7728a8f3 100644 --- a/tests/ballerina-compiler-api-test/src/test/java/io/ballerina/semantic/api/test/actions/SymbolsInResourceAccessActionTest.java +++ b/tests/ballerina-compiler-api-test/src/test/java/io/ballerina/semantic/api/test/actions/SymbolsInResourceAccessActionTest.java @@ -166,7 +166,7 @@ public Object[][] getResourceAccessActionPathPos() { @Test public void testPathSegmentOfAmbiguousResourceFunction() { Optional symbol = model.symbol(srcFile, LinePosition.from(91, 9)); - assertTrue(symbol.isEmpty()); // Resource method is ambiguous + assertTrue(symbol.isPresent()); // Resource method is ambiguous } // Utils diff --git a/tests/jballerina-debugger-integration-test/src/main/java/org/ballerinalang/debugger/test/utils/DebugTestRunner.java b/tests/jballerina-debugger-integration-test/src/main/java/org/ballerinalang/debugger/test/utils/DebugTestRunner.java index 1e3e39101a20..794467e67971 100644 --- a/tests/jballerina-debugger-integration-test/src/main/java/org/ballerinalang/debugger/test/utils/DebugTestRunner.java +++ b/tests/jballerina-debugger-integration-test/src/main/java/org/ballerinalang/debugger/test/utils/DebugTestRunner.java @@ -35,6 +35,7 @@ import org.eclipse.lsp4j.debug.OutputEventArguments; import org.eclipse.lsp4j.debug.OutputEventArgumentsCategory; import org.eclipse.lsp4j.debug.PauseArguments; +import org.eclipse.lsp4j.debug.RestartArguments; import org.eclipse.lsp4j.debug.ScopesArguments; import org.eclipse.lsp4j.debug.ScopesResponse; import org.eclipse.lsp4j.debug.SetBreakpointsArguments; @@ -153,7 +154,7 @@ public void initDebugSession(DebugUtils.DebuggeeExecutionKind executionKind) thr * * @param executionKind Defines ballerina command type to be used to launch the debuggee.(If set to null, adapter * will try to attach to the debuggee, instead of launching) - * @param terminalKind The terminal type, if the debug session should be launched in a separate terminal + * @param terminalKind The terminal type, if the debug session should be launched in a separate terminal * @throws BallerinaTestException if any exception is occurred during initialization. */ public boolean initDebugSession(DebugUtils.DebuggeeExecutionKind executionKind, String terminalKind) @@ -384,6 +385,21 @@ public void pauseProgram(int threadId) throws BallerinaTestException { } } + /** + * Restarts the execution of the debuggee program. + * + * @throws BallerinaTestException if an error occurs when resuming program. + */ + public void restartProgram() throws BallerinaTestException { + try { + RestartArguments restartArgs = new RestartArguments(); + debugClientConnector.getRequestManager().restart(restartArgs); + } catch (Exception e) { + LOGGER.warn("Restart request failed", e); + throw new BallerinaTestException("Restart request failed", e); + } + } + /** * Waits for a debug hit within a given time. * diff --git a/tests/jballerina-debugger-integration-test/src/main/java/org/ballerinalang/debugger/test/utils/client/DAPRequestManager.java b/tests/jballerina-debugger-integration-test/src/main/java/org/ballerinalang/debugger/test/utils/client/DAPRequestManager.java index a54ee255bd0e..877cfd545c8f 100644 --- a/tests/jballerina-debugger-integration-test/src/main/java/org/ballerinalang/debugger/test/utils/client/DAPRequestManager.java +++ b/tests/jballerina-debugger-integration-test/src/main/java/org/ballerinalang/debugger/test/utils/client/DAPRequestManager.java @@ -33,6 +33,7 @@ import org.eclipse.lsp4j.debug.OutputEventArguments; import org.eclipse.lsp4j.debug.PauseArguments; import org.eclipse.lsp4j.debug.ProcessEventArguments; +import org.eclipse.lsp4j.debug.RestartArguments; import org.eclipse.lsp4j.debug.RunInTerminalRequestArguments; import org.eclipse.lsp4j.debug.RunInTerminalRequestArgumentsKind; import org.eclipse.lsp4j.debug.RunInTerminalResponse; @@ -273,6 +274,19 @@ public void pause(PauseArguments args, long timeoutMillis) throws Exception { } } + public void restart(RestartArguments args) throws Exception { + restart(args, DefaultTimeouts.RESTART.getValue()); + } + + public void restart(RestartArguments args, long timeoutMillis) throws Exception { + if (checkStatus()) { + CompletableFuture resp = server.restart(args); + resp.get(timeoutMillis, TimeUnit.MILLISECONDS); + } else { + throw new IllegalStateException("DAP request manager is not active"); + } + } + public void disconnect(DisconnectArguments args) throws Exception { disconnect(args, DefaultTimeouts.DISCONNECT.getValue()); } @@ -381,6 +395,7 @@ private enum DefaultTimeouts { STEP_OUT(5000), RESUME(5000), PAUSE(5000), + RESTART(10000), DISCONNECT(5000); private final long value; diff --git a/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/BreakpointVerificationTest.java b/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/BreakpointVerificationTest.java index 4e4603e10776..6875f39333c1 100644 --- a/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/BreakpointVerificationTest.java +++ b/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/BreakpointVerificationTest.java @@ -33,6 +33,7 @@ import java.nio.file.Path; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Optional; @@ -63,13 +64,17 @@ public void setup() { @Test(description = "Test to assert runtime verification on breakpoints which were added before starting a " + "debug session") - public void testInitialBreakpointVerification() throws BallerinaTestException { + public void testBasicBreakpointVerification() throws BallerinaTestException { // Adds breakpoints for all the lines in the source. for (int line = 1; line <= SRC_LINE_COUNT; line++) { debugTestRunner.addBreakPoint(new BallerinaTestDebugPoint(debugTestRunner.testEntryFilePath, line)); } - debugTestRunner.initDebugSession(DebugUtils.DebuggeeExecutionKind.RUN); + HashMap extendedCapabilities = new HashMap<>(); + extendedCapabilities.put("supportsBreakpointVerification", true); + HashMap launchConfigs = new HashMap<>(); + launchConfigs.put("capabilities", extendedCapabilities); + debugTestRunner.initDebugSession(DebugUtils.DebuggeeExecutionKind.RUN, launchConfigs); Pair debugHitInfo = debugTestRunner.waitForDebugHit(25000); debugTestRunner.resumeProgram(debugHitInfo.getRight(), DebugTestRunner.DebugResumeKind.NEXT_BREAKPOINT); debugHitInfo = debugTestRunner.waitForDebugHit(10000); @@ -82,7 +87,7 @@ public void testInitialBreakpointVerification() throws BallerinaTestException { // retrieves all the 'breakpoint' events received from the server, which indicates breakpoint verification // status changes. List changedBreakpoints = debugTestRunner.waitForModifiedBreakpoints(2000); - assertBreakpointChanges(changedBreakpoints); + assertBreakpoints(changedBreakpoints); } @Test(description = "Test to assert runtime verification on breakpoints which are getting added on-the-fly " + @@ -90,7 +95,12 @@ public void testInitialBreakpointVerification() throws BallerinaTestException { public void testOnTheFlyBreakpointVerification() throws BallerinaTestException { // adds one initial breakpoint and run debug session until the breakpoint is reached. debugTestRunner.addBreakPoint(new BallerinaTestDebugPoint(debugTestRunner.testEntryFilePath, 27)); - debugTestRunner.initDebugSession(DebugUtils.DebuggeeExecutionKind.RUN); + + HashMap extendedCapabilities = new HashMap<>(); + extendedCapabilities.put("supportsBreakpointVerification", true); + HashMap launchConfigs = new HashMap<>(); + launchConfigs.put("capabilities", extendedCapabilities); + debugTestRunner.initDebugSession(DebugUtils.DebuggeeExecutionKind.RUN, launchConfigs); Pair debugHitInfo = debugTestRunner.waitForDebugHit(25000); Assert.assertEquals(debugHitInfo.getLeft(), new BallerinaTestDebugPoint(debugTestRunner.testEntryFilePath, 27)); @@ -108,10 +118,41 @@ public void testOnTheFlyBreakpointVerification() throws BallerinaTestException { breakpoint.getLine(), breakpoint.isVerified())) .toList(); - assertBreakpointChanges(breakPoints); + assertBreakpoints(breakPoints); } - private void assertBreakpointChanges(List breakpoints) { + @Test(description = "Test to assert runtime verification on breakpoints when the debug client doesn't support " + + "breakpoint verification") + public void testNegativeBreakpointVerification() throws BallerinaTestException { + // Adds breakpoints for all the lines in the source. + for (int line = 1; line <= SRC_LINE_COUNT; line++) { + debugTestRunner.addBreakPoint(new BallerinaTestDebugPoint(debugTestRunner.testEntryFilePath, line)); + } + + HashMap extendedCapabilities = new HashMap<>(); + extendedCapabilities.put("supportsBreakpointVerification", false); + HashMap launchConfigs = new HashMap<>(); + launchConfigs.put("capabilities", extendedCapabilities); + debugTestRunner.initDebugSession(DebugUtils.DebuggeeExecutionKind.RUN, launchConfigs); + Pair debugHitInfo = debugTestRunner.waitForDebugHit(25000); + debugTestRunner.resumeProgram(debugHitInfo.getRight(), DebugTestRunner.DebugResumeKind.NEXT_BREAKPOINT); + debugHitInfo = debugTestRunner.waitForDebugHit(10000); + debugTestRunner.resumeProgram(debugHitInfo.getRight(), DebugTestRunner.DebugResumeKind.NEXT_BREAKPOINT); + debugHitInfo = debugTestRunner.waitForDebugHit(10000); + debugTestRunner.resumeProgram(debugHitInfo.getRight(), DebugTestRunner.DebugResumeKind.NEXT_BREAKPOINT); + debugHitInfo = debugTestRunner.waitForDebugHit(10000); + Assert.assertEquals(debugHitInfo.getLeft(), new BallerinaTestDebugPoint(debugTestRunner.testEntryFilePath, 27)); + + // retrieves all the 'breakpoint' events received from the server, which indicates breakpoint verification + // status changes. + try { + List changedBreakpoints = debugTestRunner.waitForModifiedBreakpoints(2000); + } catch (BallerinaTestException ignored) { + // expected exception because the debug client doesn't support breakpoint verification. + } + } + + private void assertBreakpoints(List breakpoints) { // if statement assertVerifiedBreakpoints(breakpoints, IF_STATEMENT_START, 7, true, true, true, true, false, true, false); // while statement @@ -120,7 +161,6 @@ private void assertBreakpointChanges(List breakpoints) assertVerifiedBreakpoints(breakpoints, MATCH_STMT_START, 5, true, true, true, false, false); // documentation statement assertVerifiedBreakpoints(breakpoints, DOCUMENTATION_START, 4, false, false, false, false); - // enum declaration assertVerifiedBreakpoints(breakpoints, ENUM_DCLN_START, 5, false, false, false, false, false); // class definition diff --git a/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/DebugInstructionTest.java b/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/DebugInstructionTest.java index af1b60f54820..070222bad15d 100644 --- a/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/DebugInstructionTest.java +++ b/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/DebugInstructionTest.java @@ -131,7 +131,7 @@ public void debugPauseTest() throws BallerinaTestException { debugTestRunner.addBreakPoint(new BallerinaTestDebugPoint(mainFilePath, 18)); debugTestRunner.initDebugSession(DebugUtils.DebuggeeExecutionKind.RUN); Pair debugHitInfo = debugTestRunner.waitForDebugHit(25000); - Assert.assertEquals(debugHitInfo.getLeft(), new BallerinaTestDebugPoint(mainFilePath, 18)); + Assert.assertEquals(debugHitInfo.getLeft(), debugTestRunner.testBreakpoints.get(0)); Thread[] activeThreads = debugTestRunner.fetchThreads(); if (activeThreads.length == 0) { throw new BallerinaTestException("Failed to retrieve active threads in the program VM"); @@ -155,6 +155,32 @@ public void debugPauseTest() throws BallerinaTestException { || debugHitInfo.getLeft().equals(new BallerinaTestDebugPoint(mainFilePath, 21))); } + @Test(description = "Tests whether the debugger honors restart requests") + public void debugRestartTest() throws BallerinaTestException { + String testProjectName = "debug-instruction-tests-1"; + String testModuleFileName = "main.bal"; + debugTestRunner = new DebugTestRunner(testProjectName, testModuleFileName, true); + Path mainFilePath = debugTestRunner.testEntryFilePath; + + debugTestRunner.addBreakPoint(new BallerinaTestDebugPoint(mainFilePath, 30)); + debugTestRunner.addBreakPoint(new BallerinaTestDebugPoint(mainFilePath, 34)); + debugTestRunner.initDebugSession(DebugUtils.DebuggeeExecutionKind.RUN); + + // Initial debug hit + Pair debugHitInfo = debugTestRunner.waitForDebugHit(15000); + Assert.assertEquals(debugHitInfo.getLeft(), debugTestRunner.testBreakpoints.get(0)); + + // Resume the program and hit the next debug point. + debugTestRunner.resumeProgram(debugHitInfo.getRight(), DebugTestRunner.DebugResumeKind.NEXT_BREAKPOINT); + debugHitInfo = debugTestRunner.waitForDebugHit(10000); + Assert.assertEquals(debugHitInfo.getLeft(), debugTestRunner.testBreakpoints.get(1)); + + // Restart the program and check whether the debugger hits the first debug point again. + debugTestRunner.restartProgram(); + debugHitInfo = debugTestRunner.waitForDebugHit(15000); + Assert.assertEquals(debugHitInfo.getLeft(), debugTestRunner.testBreakpoints.get(0)); + } + @Override @AfterMethod(alwaysRun = true) public void cleanUp() { diff --git a/tests/jballerina-integration-test/src/test/resources/troubleshoot/strandDump/testOutputs/balProgram1StrandDumpRegEx.txt b/tests/jballerina-integration-test/src/test/resources/troubleshoot/strandDump/testOutputs/balProgram1StrandDumpRegEx.txt index 10f896a7b1cb..dc9d80e12598 100644 --- a/tests/jballerina-integration-test/src/test/resources/troubleshoot/strandDump/testOutputs/balProgram1StrandDumpRegEx.txt +++ b/tests/jballerina-integration-test/src/test/resources/troubleshoot/strandDump/testOutputs/balProgram1StrandDumpRegEx.txt @@ -3,13 +3,13 @@ Ballerina Strand Dump \[\d*/\d*/\d* \d*:\d*:\d*\] Total Strand count \t\t\t:\t9 -Total Isolated Worker count \t\t:\t2 +Total Isolated Strand count \t\t:\t2 -Total Non Isolated Worker count \t\t:\t7 +Total Non Isolated Strand count \t\t:\t7 ================================================================ -Isolated Workers: +Isolated Strands: \tStrand #\d* \"\w*\" : \t\tat balProgram1.\$lambda\$_0\(balProgram1.bal:\d*\) @@ -19,7 +19,7 @@ Isolated Workers: \t\tat balProgram1.\$lambda\$_1\(balProgram1.bal:\d*\) \t\t lambdas.\$_generated1balProgram1.\$lambda\$_1\$lambda1\$\(balProgram1.bal:\d*\) -Non Isolated Workers: +Non Isolated Strands: \tStrand #\d* \"\w*\" : \t\tat balProgram1.\$lambda\$_2\(balProgram1.bal:\d*\) diff --git a/tests/jballerina-integration-test/src/test/resources/troubleshoot/strandDump/testOutputs/balTestStrandDumpRegEx.txt b/tests/jballerina-integration-test/src/test/resources/troubleshoot/strandDump/testOutputs/balTestStrandDumpRegEx.txt index ba3f76332983..a54e32ceef46 100644 --- a/tests/jballerina-integration-test/src/test/resources/troubleshoot/strandDump/testOutputs/balTestStrandDumpRegEx.txt +++ b/tests/jballerina-integration-test/src/test/resources/troubleshoot/strandDump/testOutputs/balTestStrandDumpRegEx.txt @@ -3,15 +3,15 @@ Ballerina Strand Dump \[\d*/\d*/\d* \d*:\d*:\d*\] Total Strand count \t\t\t:\t7 -Total Isolated Worker count \t\t:\t0 +Total Isolated Strand count \t\t:\t0 -Total Non Isolated Worker count \t\t:\t7 +Total Non Isolated Strand count \t\t:\t7 ================================================================ -Isolated Workers: +Isolated Strands: -Non Isolated Workers: +Non Isolated Strands: \tStrand #\d* \"\w*\" : \t\tat testOrg.testPackageWithModules.0.main.bar\(main.bal:\d*\) @@ -63,4 +63,3 @@ Non Isolated Workers: \t\tat ballerina.lang&0046runtime.0.runtime.sleep\(runtime.bal:\d*\) \t\t testOrg.testPackageWithModules\$test.0.tests.main_test.\$lambda\$_11\(tests/main_test.bal:\d*\) \t\t testOrg.testPackageWithModules\$test.0.lambdas.\$_generated2tests.main_test.\$lambda\$_11\$lambda1\$\(tests/main_test.bal:\d*\) - diff --git a/tests/jballerina-integration-test/src/test/resources/troubleshoot/strandDump/testOutputs/testPackageWithModulesStrandDumpRegEx.txt b/tests/jballerina-integration-test/src/test/resources/troubleshoot/strandDump/testOutputs/testPackageWithModulesStrandDumpRegEx.txt index 20934b99a4cc..4f22b6bf1c0c 100644 --- a/tests/jballerina-integration-test/src/test/resources/troubleshoot/strandDump/testOutputs/testPackageWithModulesStrandDumpRegEx.txt +++ b/tests/jballerina-integration-test/src/test/resources/troubleshoot/strandDump/testOutputs/testPackageWithModulesStrandDumpRegEx.txt @@ -3,13 +3,13 @@ Ballerina Strand Dump \[\d*/\d*/\d* \d*:\d*:\d*\] Total Strand count \t\t\t:\t17 -Total Isolated Worker count \t\t:\t4 +Total Isolated Strand count \t\t:\t4 -Total Non Isolated Worker count \t\t:\t13 +Total Non Isolated Strand count \t\t:\t13 ================================================================ -Isolated Workers: +Isolated Strands: \tStrand #\d* \"\w*\" : \t\tat ballerina.lang&0046runtime.0.runtime.sleep\(runtime.bal:\d*\) @@ -32,7 +32,7 @@ Isolated Workers: \t\t testOrg.testPackageWithModules&0046anotherutils.0.creators.\$_function_calls.call\(Unknown Source\) \t\t testOrg.testPackageWithModules.0.lambdas.\$_generated1main.entryfunc\$lambda\$2\$\(main.bal:\d*\) -Non Isolated Workers: +Non Isolated Strands: \tStrand #\d* \"\w*\" : \t\tat testOrg.testPackageWithModules.0.main.\$lambda\$_4\(main.bal:\d*\) diff --git a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/action/ClientResourceAccessActionNegativeTest.java b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/action/ClientResourceAccessActionNegativeTest.java index 776589197342..0dc72446599e 100644 --- a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/action/ClientResourceAccessActionNegativeTest.java +++ b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/action/ClientResourceAccessActionNegativeTest.java @@ -124,18 +124,10 @@ public void testClientResourceCallNegative() { "too many arguments in call to 'post()'", 90, 13); validateError(clientResourceAccessNegative, index++, "too many arguments in call to 'post()'", 91, 13); - validateError(clientResourceAccessNegative, index++, "client resource access action is not yet " + - "supported when the corresponding resource method is ambiguous", 145, 13); - validateError(clientResourceAccessNegative, index++, "client resource access action is not yet " + - "supported when the corresponding resource method is ambiguous", 146, 13); - validateError(clientResourceAccessNegative, index++, "client resource access action is not yet " + - "supported when the corresponding resource method is ambiguous", 147, 13); validateError(clientResourceAccessNegative, index++, "client resource access action is not yet " + "supported when the corresponding resource method is ambiguous", 148, 13); validateError(clientResourceAccessNegative, index++, "client resource access action is not yet " + "supported when the corresponding resource method is ambiguous", 149, 13); - validateError(clientResourceAccessNegative, index++, "client resource access action is not yet " + - "supported when the corresponding resource method is ambiguous", 150, 13); validateError(clientResourceAccessNegative, index++, "client resource access action is not yet " + "supported when the corresponding resource method is ambiguous", 151, 13); validateError(clientResourceAccessNegative, index++, "client resource access action is not yet " +