Skip to content

Commit

Permalink
Add a mechanism to provide the thread number as an operation parameter (
Browse files Browse the repository at this point in the history
  • Loading branch information
ndkoval authored Sep 7, 2020
1 parent d6804d6 commit 2ec079a
Show file tree
Hide file tree
Showing 12 changed files with 161 additions and 121 deletions.
4 changes: 2 additions & 2 deletions src/main/java/org/jetbrains/kotlinx/lincheck/Actor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ package org.jetbrains.kotlinx.lincheck

import org.jetbrains.kotlinx.lincheck.annotations.*
import java.lang.reflect.Method
import kotlin.coroutines.Continuation
import kotlin.reflect.jvm.*

/**
* The actor entity describe the operation with its parameters
Expand All @@ -46,4 +46,4 @@ data class Actor @JvmOverloads constructor(
val isSuspendable = method.isSuspendable()
}

fun Method.isSuspendable() = parameterTypes.isNotEmpty() && (kotlin.coroutines.Continuation::class.java).isAssignableFrom(parameterTypes.last())
fun Method.isSuspendable(): Boolean = kotlinFunction?.isSuspend ?: false
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ import java.lang.AssertionError

class LincheckAssertionError(
failure: LincheckFailure
) : AssertionError(failure)
) : AssertionError("\n" + failure)
18 changes: 0 additions & 18 deletions src/main/java/org/jetbrains/kotlinx/lincheck/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,24 +49,6 @@ fun chooseSequentialSpecification(sequentialSpecificationByUser: Class<*>?, test
if (sequentialSpecificationByUser === DummySequentialSpecification::class.java || sequentialSpecificationByUser == null) testClass
else sequentialSpecificationByUser

/**
* Creates instance of cost counter class using the (int relaxationFactor) constructor.
*/
fun createCostCounterInstance(costCounter: Class<*>, relaxationFactor: Int): Any {
try {
return costCounter.getDeclaredConstructor(Int::class.javaPrimitiveType).newInstance(relaxationFactor)
} catch (e: Exception) {
e.catch(
IllegalStateException::class.java,
IllegalAccessException::class.java,
NoSuchMethodException::class.java,
InvocationTargetException::class.java
) {
throw IllegalStateException("Cost counter class should have '(relaxationFactor: Int)' constructor", e)
}
}
}

internal fun executeActor(testInstance: Any, actor: Actor) = executeActor(testInstance, actor, null)

/**
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*-
* #%L
* Lincheck
* %%
* Copyright (C) 2019 - 2020 JetBrains s.r.o.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.jetbrains.kotlinx.lincheck.execution

import org.jetbrains.kotlinx.lincheck.*
import org.jetbrains.kotlinx.lincheck.paramgen.*
import java.lang.reflect.*
import kotlin.random.*

/**
* Implementations of this class generate [actors][Actor]
* using [parameter generators][ParameterGenerator].
*/
class ActorGenerator(
private val method: Method,
private val parameterGenerators: List<ParameterGenerator<*>>,
private val handledExceptions: List<Class<out Throwable?>>,
val useOnce: Boolean,
cancellableOnSuspension: Boolean,
val allowExtraSuspension: Boolean
) {
private val cancellableOnSuspension = cancellableOnSuspension && isSuspendable

fun generate(threadId: Int): Actor {
val parameters = parameterGenerators
.map { it.generate() }
.map { if (it === THREAD_ID_TOKEN) threadId else it }
val cancelOnSuspension = cancellableOnSuspension and DETERMINISTIC_RANDOM.nextBoolean()
return Actor(method, parameters, handledExceptions,
cancelOnSuspension, allowExtraSuspension)
}

val isSuspendable: Boolean get() = method.isSuspendable()
override fun toString() = method.toString()
}

private val DETERMINISTIC_RANDOM = Random(42)
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import java.util.*;
import java.util.stream.*;

import static org.jetbrains.kotlinx.lincheck.ActorKt.isSuspendable;
import static org.jetbrains.kotlinx.lincheck.ReporterKt.appendExecutionScenario;

/**
Expand Down Expand Up @@ -76,7 +75,7 @@ public int getThreads() {
* Returns `true` if there is at least one suspendable actor in the generated scenario
*/
public boolean hasSuspendableActors() {
return Stream.concat(parallelExecution.stream().flatMap(Collection::stream), postExecution.stream()).anyMatch(actor -> isSuspendable(actor.getMethod()));
return Stream.concat(parallelExecution.stream().flatMap(Collection::stream), postExecution.stream()).anyMatch(Actor::isSuspendable);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ public RandomExecutionGenerator(CTestConfiguration testConfiguration, CTestStruc
public ExecutionScenario nextExecution() {
// Create init execution part
List<ActorGenerator> validActorGeneratorsForInit = testStructure.actorGenerators.stream()
.filter(ag -> !ag.useOnce() && !ag.isSuspendable()).collect(Collectors.toList());
.filter(ag -> !ag.getUseOnce() && !ag.isSuspendable()).collect(Collectors.toList());
List<Actor> initExecution = new ArrayList<>();
for (int i = 0; i < testConfiguration.actorsBefore && !validActorGeneratorsForInit.isEmpty(); i++) {
ActorGenerator ag = validActorGeneratorsForInit.get(random.nextInt(validActorGeneratorsForInit.size()));
initExecution.add(ag.generate());
initExecution.add(ag.generate(0));
}
// Create parallel execution part
// Construct non-parallel groups and parallel one
Expand All @@ -61,9 +61,9 @@ public ExecutionScenario nextExecution() {

List<List<Actor>> parallelExecution = new ArrayList<>();
List<ThreadGen> threadGens = new ArrayList<>();
for (int i = 0; i < testConfiguration.threads; i++) {
for (int t = 0; t < testConfiguration.threads; t++) {
parallelExecution.add(new ArrayList<>());
threadGens.add(new ThreadGen(i, testConfiguration.actorsPerThread));
threadGens.add(new ThreadGen(t, testConfiguration.actorsPerThread));
}
for (int i = 0; i < nonParallelGroups.size(); i++) {
threadGens.get(i % threadGens.size()).nonParallelActorGenerators
Expand All @@ -86,7 +86,7 @@ public ExecutionScenario nextExecution() {
agen = getActorGenFromGroup(parallelGroup,
aGenIndex - threadGen.nonParallelActorGenerators.size());
}
parallelExecution.get(threadGen.threadNumber).add(agen.generate());
parallelExecution.get(threadGen.iThread).add(agen.generate(threadGen.iThread + 1));
if (--threadGen.left == 0)
it.remove();
}
Expand All @@ -101,7 +101,7 @@ public ExecutionScenario nextExecution() {
leftActorGenerators.addAll(threadGen.nonParallelActorGenerators);
for (int i = 0; i < testConfiguration.actorsAfter && !leftActorGenerators.isEmpty(); i++) {
ActorGenerator agen = getActorGenFromGroup(leftActorGenerators, random.nextInt(leftActorGenerators.size()));
postExecution.add(agen.generate());
postExecution.add(agen.generate(testConfiguration.threads + 1));
}
} else {
postExecution = Collections.emptyList();
Expand All @@ -111,18 +111,18 @@ public ExecutionScenario nextExecution() {

private ActorGenerator getActorGenFromGroup(List<ActorGenerator> aGens, int index) {
ActorGenerator aGen = aGens.get(index);
if (aGen.useOnce())
if (aGen.getUseOnce())
aGens.remove(index);
return aGen;
}

private class ThreadGen {
private static class ThreadGen {
final List<ActorGenerator> nonParallelActorGenerators = new ArrayList<>();
int threadNumber;
int iThread;
int left;

ThreadGen(int threadNumber, int nActors) {
this.threadNumber = threadNumber;
ThreadGen(int iThread, int nActors) {
this.iThread = iThread;
this.left = nActors;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*-
* #%L
* Lincheck
* %%
* Copyright (C) 2019 - 2020 JetBrains s.r.o.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.jetbrains.kotlinx.lincheck.paramgen

/**
* This generator puts the number of the
* executing thread as the parameter value.
* The `0`-th thread specifies the init part
* of the execution, while the `t+1`-th thread
* references the post part (here we assume that
* the parallel part has `t` threads).
*
* Note, that this API is unstable and is subject to change.
*/
class ThreadIdGen(configuration: String) : ParameterGenerator<Any> {
override fun generate() = THREAD_ID_TOKEN
}

internal val THREAD_ID_TOKEN = Any()
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ open class ParallelThreadsRunner(
private val runnerHash = this.hashCode() // helps to distinguish this runner threads from others
private val executor = FixedActiveThreadsExecutor(scenario.threads, runnerHash)

private val completions = List(scenario.threads) { threadId ->
List(scenario.parallelExecution[threadId].size) { Completion(threadId) }
private val completions = List(scenario.threads) { t ->
List(scenario.parallelExecution[t].size) { Completion(t) }
}

private val testThreadExecutions = Array(scenario.threads) { t ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import java.util.ArrayList;
import java.util.List;

import static org.jetbrains.kotlinx.lincheck.ActorKt.isSuspendable;
import static org.objectweb.asm.Opcodes.*;
import static org.objectweb.asm.Type.*;

Expand Down Expand Up @@ -328,7 +327,7 @@ private static void storeExceptionResultFromThrowable(GeneratorAdapter mv, int r
mv.arrayStore(RESULT_TYPE);
}

private static void storeExceptionResultFromSuspendableThrowable(GeneratorAdapter mv, int resLocal, int iLocal, int threadId, int actorId) {
private static void storeExceptionResultFromSuspendableThrowable(GeneratorAdapter mv, int resLocal, int iLocal, int iThread, int actorId) {
int eLocal = mv.newLocal(THROWABLE_TYPE);
mv.storeLocal(eLocal);
mv.loadLocal(resLocal);
Expand All @@ -339,7 +338,7 @@ private static void storeExceptionResultFromSuspendableThrowable(GeneratorAdapte
mv.checkCast(PARALLEL_THREADS_RUNNER_TYPE);
// Load exception result
mv.loadLocal(eLocal);
mv.push(threadId);
mv.push(iThread);
mv.push(actorId);
// Process result
mv.invokeVirtual(PARALLEL_THREADS_RUNNER_TYPE, PARALLEL_THREADS_RUNNER_PROCESS_INVOCATION_RESULT_METHOD);
Expand All @@ -351,7 +350,7 @@ private static void loadArguments(GeneratorAdapter mv, Actor actor, List<Object>
for (int j = 0; j < nArguments; j++) {
pushArgumentOnStack(mv, objArgs, actor.getArguments().toArray()[j], actor.getMethod().getParameterTypes()[j]);
}
if (isSuspendable(actor.getMethod())) {
if (actor.isSuspendable()) {
pushArgumentOnStack(mv, objArgs, completion, Continuation.class);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ import org.jetbrains.kotlinx.lincheck.chooseSequentialSpecification
/**
* Options for [stress][StressStrategy] strategy.
*/
class StressOptions : Options<StressOptions, StressCTestConfiguration>() {
open class StressOptions : Options<StressOptions, StressCTestConfiguration>() {
protected var invocationsPerIteration = StressCTestConfiguration.DEFAULT_INVOCATIONS
protected var addWaits = true
private var addWaits = true

/**
* Run each test scenario `invocations` times.
Expand Down
Loading

0 comments on commit 2ec079a

Please sign in to comment.