-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Run init/post parts of the scenario in the 1st thread #146
Conversation
@ndkoval ready for review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eupp, great job, thanks! I've suggested several improvements, please address them, and I'll review the PR again. Please also check how this change affects the performance.
src/jvm/test/resources/expected_logs/switch_as_first_method_event.txt
Outdated
Show resolved
Hide resolved
src/jvm/test/resources/expected_logs/coroutine_cancellation.txt
Outdated
Show resolved
Hide resolved
strategy.beforePart(part) | ||
} | ||
|
||
fun afterPart(part: ExecutionPart) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we provide executeInitPart
, executeParallelPart
, and executePostPart
functions? Would it make the API more clear? Are there other solutions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes we can do that.
But then, we have similar methods in the Strategy
API. They are used to inform strategy about start of execution of particular scenario part. Currently, implementations of these methods in Strategy inheritors reset some internal state of the strategy between different scenario parts.
So how would you envision this API in Strategy
then?
Or should we change Runner
API, but left corresponding part of Strategy
API as is?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know which design is better. Please think about possible options, and let's discuss them (Slack?).
Also, consider making the strategy responsible for executing the init/post parts -- in this case, the runner API will be simpler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I revisited it, but failed to completely remove beforePart
and afterPart
methods from the API.
As for the idea to make strategy responsible for running init/post parts, it seems there is also a couple of problems with it:
- Currently, the runner owns the thread pool, it is private property of runner. Strategy cannot directly send tasks to the runner thread pool with the current API.
- Both
StressStrategy
andModelCheckingStrategy
use the same init/post parts running logic (which is placed inParallelThreadsRunner
class that they both use). If we want to move that logic intoStrategy
we need to add another common class or something for both strategies to share this init/post parts running logic.
I propose to postpone this. Let's collect all the issues with the current Runner API in one places and discuss how they can be solved by some new API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We now have only beforePart
, don't we?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please document beforePart
and leave the rest for the future.
src/jvm/test/resources/expected_logs/switch_as_first_method_event.txt
Outdated
Show resolved
Hide resolved
@@ -79,14 +70,30 @@ private fun splitToColumns(nThreads: Int, traceRepresentation: List<TraceEventRe | |||
*/ | |||
private fun constructTraceGraph(scenario: ExecutionScenario, results: ExecutionResult?, trace: Trace): TraceNode? { | |||
val tracePoints = trace.trace | |||
// remap scenario actors: put init/post part into first thread | |||
val remappedScenario = Array(scenario.threads) { i -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see a lot of logic here and there related to the init/post parts. Consider incorporating init/post parts into the first thread scenario part (as you depict when printing an error message), making the strategy responsible for executing init/post parts in the first thread at the beginning/end of the scenario. Would it simplify the overall design?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it simplify the overall design?
Yes, for sure. I agree that currently there is a lot of ad-hoc hacks for handling init/post parts in various places.
The only reason I have not fix it is because that would be a huge refactoring affecting a lot of places throughout the codebase.
There are a lot of places in the code that implicitly assume that init/post parts are executed in their own "virtual" threads (e.g. model checking code responsible for counting executed actors).
In order solve this problem nicely, we would also need to modify ExecutionScenario
and similar classes (e.g. ExecutionResult
, etc), i.e. all classes that mention init/post parts. That, in turn, would also require to review all the places where ExecutionScenario
is used (and there are a lot of them), etc.
Besides, there are several possible solutions on how to handle this.
I think this problem is worth openning a separate issue where we can discuss pros/cons of various solutions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see why you must modify the ExecutionScenario
class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tried to address this problem and simplify thread and actors enumeration by putting all the related logic in ExecutionScenario
and ExecutionResult
classes.
There is still some room for improvement. For example, I was not able to refactor happens-before clock calculation logic --- it just too complicated and convoluted (because of bytecode generation). I think we first need to refactor TestThreadExecutionGenerator
class and convert as much code as possible from bytecode generation to regular Kotlin code. I'll open an issue for this problem.
For now I come up with an intermediate solution that just rebuilds clocks inside ExecutionResult
class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this discussion up-to-date?
src/jvm/main/org/jetbrains/kotlinx/lincheck/runner/FixedActiveThreadsExecutor.kt
Outdated
Show resolved
Hide resolved
src/jvm/main/org/jetbrains/kotlinx/lincheck/runner/FixedActiveThreadsExecutor.kt
Show resolved
Hide resolved
src/jvm/main/org/jetbrains/kotlinx/lincheck/verifier/linearizability/LinearizabilityVerifier.kt
Show resolved
Hide resolved
src/jvm/main/org/jetbrains/kotlinx/lincheck/execution/ExecutionScenario.kt
Outdated
Show resolved
Hide resolved
src/jvm/main/org/jetbrains/kotlinx/lincheck/execution/ExecutionResult.kt
Outdated
Show resolved
Hide resolved
src/jvm/main/org/jetbrains/kotlinx/lincheck/execution/ExecutionScenario.kt
Outdated
Show resolved
Hide resolved
I rebased the branch on recent As for the performance, according to the CI builds, CC @ndkoval |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the updates! Please address the remaining comments from the previous review and several new ones. After that, the PR should be ready to merge.
src/jvm/main/org/jetbrains/kotlinx/lincheck/execution/ExecutionResult.kt
Outdated
Show resolved
Hide resolved
src/jvm/main/org/jetbrains/kotlinx/lincheck/execution/ExecutionScenario.kt
Show resolved
Hide resolved
src/jvm/main/org/jetbrains/kotlinx/lincheck/runner/ParallelThreadsRunner.kt
Outdated
Show resolved
Hide resolved
src/jvm/main/org/jetbrains/kotlinx/lincheck/runner/ParallelThreadsRunner.kt
Outdated
Show resolved
Hide resolved
* List containing for each thread its list of actors. | ||
* Init and post parts are placed in the 1st thread. | ||
*/ | ||
val threads: List<List<Actor>> = (0 until nThreads).map { i -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You do not need to copy the scenario. More importantly, you do not need this threads
property -- just add a get(threadId, actorId)
function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, this is not that simple :(
In some cases, I also need, for example, to get the size of the particular thread (including init/post parts for the 1st thread). By having threads
as a list, I have all the usual API for threads[0]
, e.g. size
, indices
, etc.
Otherwise, I need to somehow duplicate this API in ExecutionScenario
class.
I actually tried the approach you suggest at first, but the code turned out to be more complicated.
If the memory consumption is a concern, we can also try another approach. We can have init
, post
and parallel[i]
part lists as sub-lists of threads[i]
lists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. Then maybe make ExecutionScenario : List<List<Actor>> by threads
. Will it make code easier to read?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I cannot delegate to a property not passed into constructor.
There is a workaround with private primary constructor and public secondary constructor:
https://stackoverflow.com/a/71955281/4676150
Do we want to use it here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it makes the code easier to read, yes. You decide.
.../main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingStrategy.kt
Outdated
Show resolved
Hide resolved
.../main/org/jetbrains/kotlinx/lincheck/strategy/managed/modelchecking/ModelCheckingStrategy.kt
Outdated
Show resolved
Hide resolved
Please also rebase on |
7e8ed8c
to
d86a555
Compare
…cenario and trace output formats correspondingly
…cenario and trace output formats correspondingly
Currently init and post parts of the scenario are run in the main thread. If some actor running in init/post part hungs, the lincheck hangs as well. Because of the similar reason, exceptions thrown in init/post part are not handled properly: instead of returning
UnexpectedExceptionFailure
lincheck fails with the thrown exception.This PR fixes these problems. It fixes the runner and executor classes, so that init and post parts of the scenario are also run in separate threads.
New tests to check for hung and exception handling in init/post parts are added as well.