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

Register run on stop callbacks #2402

Merged
merged 5 commits into from
Jan 24, 2018

Conversation

nimmaj
Copy link
Contributor

@nimmaj nimmaj commented Jan 21, 2018

👮🏻👮🏻👮🏻 !!!! DESCRIBE YOUR CHANGES HERE !!!! DO NOT FORGET !!!! 👮🏻👮🏻👮🏻

When writing driver based integration tests in the cordapp-template using Braid, there is a nice shutdown mechanism used by Corda to shut all of its services down without shutting down the VM. Unfortunately there is no mechanism for Braid to know about this so Braid leaves ports open and lying around and the second test is unable to run.

This PR exposes the existing runOnStop infrastructure in AbstractNode to CordaServices via ServiceHub. I initially put it in ServiceHubInternal but during conversation with Joel we moved it to ServiceHub.

I also wrote tests, admittedly driver based, in the cordapp-template, which worked fine.

PR Checklist:

prtests

  • [Yes ] If you added/changed public APIs, did you write/update the JavaDocs?

Probably need reviewing for consistency/correct nomenclature.

  • [Yes ] If the changes are of interest to application developers, have you added them to the changelog, and potentially release notes?

  • [X ] If you are contributing for the first time, please read the agreement in CONTRIBUTING.md now and add to this Pull Request that you agree to it.

I agree to the current contents of CONTRIBUTING.md.

Look forward to your feedback.

Thanks, Ben

Thanks for your code, it's appreciated! :)

@nimmaj nimmaj changed the title Nimmaj register run on stop Register run on stop callbacks Jan 21, 2018
Copy link
Contributor

@mikehearn mikehearn left a comment

Choose a reason for hiding this comment

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

Thanks for the contribution!

I've done a review of the current design below. @mnesbit has in the past mentioned that maybe we should be exposing more sophisticated lifecycle management to the service objects. For example, to prepare for a future where we can start and stop apps without starting and stopping the node itself.

*
* You should not rely on this to clean up executing flows - that's what quasar is for.
*/
fun addRunOnStop(runOnStop: () -> Any?)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe registerUnloadHandler is a better name? Again, because that way we can change when the callback is used in future?

Also - rather than use Any? which implies the result might be used for something, why not use Unit. Unit is a special type in Kotlin which is used in place of void in Java. So it will be translated to () -> void which is a more helpful API for Java users and people using other JVM languages. I realise the internal list uses Any? because that way we can jam any function at all into it, but for public API we should be more precise in our types. I think you can assign () -> Unit to () -> Any in Kotlin so it should involve no code changes on your part.

@@ -354,4 +354,13 @@ interface ServiceHub : ServicesForResolution {
* @return A new [Connection]
*/
fun jdbcSession(): Connection

/**
* Allows the registration of a callback to inform services when corda is shutting down.
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a good api doc, but I'd add the following paragraph:

"Please note that the shutdown handler is not guaranteed to be called. In production the node process may crash, be killed by the operating system and other forms of fatal termination may occur that result in this code never running. So you should use this functionality only for unit/integration testing or for code that can optimise the shutdown e.g. by cleaning up things that would otherwise trigger a slow recovery process next time the node starts."

I would also tweak the text so it talks about shutdown of the app not shutdown of the node. Subtle distinction without a difference in the current platform, but it ensures that we are not breaking semantic compatibility later if we start running this callback when an app is unloaded on the fly.

import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit

private fun checkQuasarAgent() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I realise that this is here because you encountered this problem but it's something that should be asserted inside the driver code. Can you take this out and/or move it into the implementation of the driver DSL? The idea is right, it's just the location that's wrong.

}
}

val latch = CountDownLatch(1)
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be moved into the companion object - at the top level it'll pollute the global namespace, interfering with code completion and slowing down the IDE.

checkQuasarAgent()
}

@Test
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't there an existing test file that this can be put in? NodeTests is an awfully generic name for a class that only has a single unit test in it - again, this class will show up in code completion and navigation, so it's good for it to have a specific name if it can't be merged with some other test suite.

@@ -57,6 +57,8 @@ open class MockServices private constructor(
private val initialIdentity: TestIdentity,
private val moreKeys: Array<out KeyPair>
) : ServiceHub, StateLoader by validatedTransactions {

Copy link
Contributor

Choose a reason for hiding this comment

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

No need for these extra blank lines.

@nimmaj
Copy link
Contributor Author

nimmaj commented Jan 22, 2018

Awesome comments - thanks for those. I believe I've made all the requested updates. The only contentious change is that I've renamed NodeTests rather than merged into another one. The obvious candidate to merge with is NodeKeyStoreCheckTest, but merging those two together is likely to create a suite with the name NodeTests again, arguably missing the point of the comment. I originally called my test NodeTests because I was worried about adding my test to one of the existing named tests! Do let me know your thoughts around this. Thanks for taking the time to review.

Copy link
Contributor

@mikehearn mikehearn left a comment

Choose a reason for hiding this comment

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

Thanks, that looks a lot better. I noticed one minor thing that doesn't matter much, and I asked Andras to comment on the Quasar check in Driver DSL, as he authored the code originally. After that I think we're there.

class RunOnStopTestService(serviceHub: ServiceHub) : SingletonSerializeAsToken() {

companion object {
private val log = loggerFor<RunOnStopTestService>()
Copy link
Contributor

Choose a reason for hiding this comment

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

You can use contextLogger() here - it's shorter and survives moves/renames.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's much nicer. Thanks :-)

@@ -888,6 +889,9 @@ fun <DI : DriverDSL, D : InternalDriverDSL, A> genericDriver(
val serializationEnv = setGlobalSerialization(initialiseSerialization)
val shutdownHook = addShutdownHook(driverDsl::shutdown)
try {
if (!(ManagementFactory.getRuntimeMXBean().inputArguments.any { it.contains("quasar") })) {
Copy link
Contributor

Choose a reason for hiding this comment

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

@exFalso Does this place look right to you? I suspect we should only check for this, if in-memory mode is in use - concur?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's interesting because where I've put it, I found I needed to add a line to the build.gradle to make the web server integration tests pass (they're broken in the current version of this PR - apols for that - i didn't realise that pushing to my repo would immediately build here). So I would be interested in your views.

Copy link
Contributor

Choose a reason for hiding this comment

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

@mikehearn out-of-process nodes have this argument specified explicitly elsewhere so the spawned jvms won't use the argument that's checked here. This code works anyway because we specify the agent for all tests.
Perhaps move this to startInProcessNode() to make it clear that it's a precondition of starting in-process nodes. This will delay the failure until a startNode is called, but I don't think that's an issue

@nimmaj
Copy link
Contributor Author

nimmaj commented Jan 22, 2018

Thanks. Checked in a fix for the breaking test (that may turn out to be irrelevant) and your suggested logging change which is rather nice. Tests seem to pass locally, though i'm struggling with dangling JVMs - don't know if that's normal?

Copy link
Contributor

@exFalso exFalso left a comment

Choose a reason for hiding this comment

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

Small things, otherwise LGTM. Neat PR!

@@ -888,6 +889,9 @@ fun <DI : DriverDSL, D : InternalDriverDSL, A> genericDriver(
val serializationEnv = setGlobalSerialization(initialiseSerialization)
val shutdownHook = addShutdownHook(driverDsl::shutdown)
try {
if (!(ManagementFactory.getRuntimeMXBean().inputArguments.any { it.contains("quasar") })) {
Copy link
Contributor

Choose a reason for hiding this comment

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

@mikehearn out-of-process nodes have this argument specified explicitly elsewhere so the spawned jvms won't use the argument that's checked here. This code works anyway because we specify the agent for all tests.
Perhaps move this to startInProcessNode() to make it clear that it's a precondition of starting in-process nodes. This will delay the failure until a startNode is called, but I don't think that's an issue

@@ -31,13 +31,6 @@ import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.streams.toList


private fun checkQuasarAgent() {
if (!(ManagementFactory.getRuntimeMXBean().inputArguments.any { it.contains("quasar") })) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Was about to comment that this is already done here when I noticed you updated this code as well! Thorough:)

There is one more test that replicates this behaviour, NodeStatePersistenceTests. There this check is done to decide whether to run nodes in or out of process. I believe this is incorrect and may have been left in from a debugging session. Branching on this jvm flag means that running the test from IntelliJ may result in different behaviour than running it from gradle, so I think that code can simply be removed, it should always be out-of-process.

@@ -1,5 +1,6 @@
apply plugin: 'kotlin'
apply plugin: 'java'
apply plugin: 'net.corda.plugins.quasar-utils'
Copy link
Contributor

Choose a reason for hiding this comment

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

If we move the check to startInProcessNode this will perhaps not be needed anymore

@nimmaj
Copy link
Contributor Author

nimmaj commented Jan 24, 2018

Sorry for the delay - machine was being fixed! Ok, I've moved the code as suggested and reverted the build.gradle - feels better now - thanks for the suggestion :-)

@mikehearn
Copy link
Contributor

Thanks and no need to apologise for any delays, you're the one contributing and we're the one making demands here :)

It looks good to me so I approved it. Once it passes integration testing I'll merge. Thanks for the help!

@mikehearn mikehearn merged commit d17670c into corda:master Jan 24, 2018
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.

None yet

3 participants