Skip to content
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

Draft API for the general-purpose model checker #411

Merged
merged 29 commits into from
Jan 28, 2025
Merged

Conversation

dmitrii-artuhov
Copy link
Collaborator

@dmitrii-artuhov dmitrii-artuhov commented Oct 7, 2024

Description:

PR adds function for running lincheck model checker algorithm on arbitrary code via lambda body.
Usage example:

class JunitTest {
  val invocations = 100
  @Test
  fun test() = runConcurrentTest(invocations) {
    var counter = 0
    val t1 = thread { counter++ }
    val t2 = thread { counter++ }
    t1.join(); t2.join()
    check(counter == 2) // will detect an error an throw an exception
  }
}

Additional improvements to the PR:

@dmitrii-artuhov dmitrii-artuhov requested a review from eupp October 7, 2024 14:04
@dmitrii-artuhov dmitrii-artuhov self-assigned this Oct 7, 2024
@dmitrii-artuhov dmitrii-artuhov marked this pull request as draft October 7, 2024 14:04
@eupp
Copy link
Collaborator

eupp commented Oct 7, 2024

@dmitrii-artuhov thanks for the PR.
Could you please repair the CI before we start reviewing this.

@dmitrii-artuhov dmitrii-artuhov changed the title [Draft] Lincheck new API function Lincheck new API function Oct 17, 2024
@ndkoval ndkoval changed the title Lincheck new API function API for the general-purpose model checker Nov 25, 2024
@dmitrii-artuhov dmitrii-artuhov marked this pull request as ready for review January 14, 2025 12:46
@dmitrii-artuhov dmitrii-artuhov requested a review from eupp January 14, 2025 13:01
@dmitrii-artuhov
Copy link
Collaborator Author

dmitrii-artuhov commented Jan 16, 2025

@eupp I added the testClassInstance parameter to the runConcurrentTest function, because right now I utilize a specifal wrapper class which calls the lambda and only this wrapper is recreated between iterations, not the user class which might contain fields that are modified. Since on trace collection there are in total 2 iterations, then I need to restore the fields of the actual user test class. Required functionality was added to the SnapshotTracker class, so that it restores the user test class instance between iterations.

@eupp
Copy link
Collaborator

eupp commented Jan 16, 2025

I added the testClassInstance parameter to the runConcurrentTest function

Let's please revert this change. The API should be as simple as it could be, that is the point why we adding it. It should only take the lambda to run and that's it.

I utilize a specifal wrapper class which calls the lambda and only this wrapper is recreated between iterations, not the user class which might contain fields that are modified.

To make the test work with only the lambda, please remove any state of the test class by either:

  • making the respective fields static, or
  • promoting the fields to regular variables inside lambda.

Use one method or another, depending on which makes more sense for a particular test.

This way we will eliminate any "external" state outside lambda (except static state, but it will be handled by the snapshot tracker).

In general, there is indeed a problem here --- the problem is that the lambda that we pass to the model checker should not capture any state, in order to be deterministically reproducible.
I will add this problem to the design document, and we will discuss it and possible solutions separately.

One of possible solutions is similar to what you propose --- to force the user to explicitly mention all captured objects as method parameters. But we need to discuss this solution and how to express it in the API in more detail before implementing it.

@eupp eupp force-pushed the run-with-lincheck-api branch from 3c3d5d8 to 951e45a Compare January 21, 2025 23:56
@eupp
Copy link
Collaborator

eupp commented Jan 22, 2025

The task to add support for thread pools turned out to be more challenging than anticipated, and it required non-trivial changes. I transferred it into separate PR so we can review and discuss that code separately: #447

@eupp
Copy link
Collaborator

eupp commented Jan 22, 2025

I've tried to add tests with coroutines, using custom thread pool as a dispacther, using ExecutorService.asCoroutineDispatcher(): https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/as-coroutine-dispatcher.html.

But it seems I run into bug #404 with this test, so I am going to try debug and fix it first.

@eupp eupp requested a review from ndkoval January 22, 2025 16:48
val strategy = testCfg.createStrategy(GeneralPurposeMCWrapper::class.java, scenario, null, null)
val verifier = testCfg.createVerifier()

for (i in 1..invocations) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to implement this logic again? Can't we just call options.checkImpl instead?

Copy link
Collaborator

@eupp eupp Jan 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, just using options.checkImpl currently does not work.
The problem is that we need to manually trigger the instrumentation of the passed lambda class:

ensureObjectIsTransformed(block)

Otherwise, it is not triggered automatically, with the current implementation of ensureObjectIsTransformed and ensureClassHierarchyIsTransformed. This is because instead of the lambda itself, we pass the GeneralPurposeModelCheckingWrapper object to make the whole thing work with the scenario-based Lincheck API. So only this class is transformed. Because this class does not store block lambda in any of its fields, but instead is passed as actor argument, its instrumentation is not triggered automatically.

I propose we implement the required refactoring in a separate PR, because it would touch a lot of places in the code (LinChecker.kt, runners, managed strategy, LincheckJavaAgent, etc).

In a meantime, I changed the code to use Strategy.runIteration. It is not as good, but still simplifies the code and avoids unnecessary duplication.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would storing the lambda in GeneralPurposeModelCheckingWrapper solve the issue and simplify the logic?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see why not -- GeneralPurposeModelCheckingWrapper must have an empty constructor.

*/
fun <R> runConcurrentTest(
invocations: Int = DEFAULT_INVOCATIONS_COUNT,
block: () -> R
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a test on Java, with and without invocations, to check the API.

Copy link
Collaborator

@eupp eupp Jan 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, using this API from Java is currently unsupported due to technical limitations: #445

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, so let's ignore the test. However, we need to see what the code looks like in Java.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: as we are not working on the API now, we can postpone this question.

dmitrii-artuhov and others added 18 commits January 25, 2025 00:43
Signed-off-by: Evgeniy Moiseenko <[email protected]>
Signed-off-by: Evgeniy Moiseenko <[email protected]>
Signed-off-by: Evgeniy Moiseenko <[email protected]>
Signed-off-by: Evgeniy Moiseenko <[email protected]>
Signed-off-by: Evgeniy Moiseenko <[email protected]>
Signed-off-by: Evgeniy Moiseenko <[email protected]>
@eupp eupp force-pushed the run-with-lincheck-api branch from 5ef8842 to ba7d010 Compare January 24, 2025 23:44
@eupp eupp requested a review from ndkoval January 25, 2025 01:19
eupp added 3 commits January 27, 2025 21:46
Signed-off-by: Evgeniy Moiseenko <[email protected]>
…cks)

GOD FORGIVE ME FOR THIS CODE

Signed-off-by: Evgeniy Moiseenko <[email protected]>
Signed-off-by: Evgeniy Moiseenko <[email protected]>
@@ -0,0 +1,42 @@
= Concurrent test failed =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this line, or should we start the output with the exception?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would leave it.

  1. It is a title of the whole report, explaining what happened. Starting with the exception might be confusing, as if the code itself thrown an exception.

  2. Currently there are two possible lines:

= Concurrent test failed =
= Concurrent test has hung =

Signed-off-by: Evgeniy Moiseenko <[email protected]>
@ndkoval ndkoval merged commit d5ff599 into develop Jan 28, 2025
18 checks passed
@ndkoval ndkoval deleted the run-with-lincheck-api branch January 28, 2025 09:48
@ndkoval ndkoval changed the title API for the general-purpose model checker Draft API for the general-purpose model checker Jan 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants