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

Fix LocalExecutionService ignored errors #351

Merged
merged 15 commits into from
Oct 23, 2020

Conversation

vinjana
Copy link
Contributor

@vinjana vinjana commented Oct 7, 2020

LocalExecutionService ignored errors when running asynchronously with waitFor=false. This may have been responsible for non-detection of errors during bresume calls and accumulation of PSUPS LSF-cluster-jobs.

This fix uses the AsyncExecutionResult to wrap asynchronously computed exit code, stdout, & stderr. Currently, errors are only reported via log-messages if ignoreErrors = true but produce an UnexpectedExecutionResultException if ignoreErrors = false.

Additional smaller changes are commented in the PR.


/**
* The local execution service executes commands on the local machine. For this groovy's execute() method is used.
* @author michael
* All commands are executed using Bash as interpreter!
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was not the case before. The change corrects the inconsistent behaviour.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That sounds like a pretty important (breaking?) change.
Should the comment permanently reflect this, e.g. by adding some wording like

prior to version vX.X.X, varying interpreters were used. This was normalised to consistently use bash starting at version

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. I also did not have updated the changelogs. This should be fixed. The choice to do it like this is justified by that for synchronous execution also bash -c is used (via LocalExecutionHelper in RoddyToolLib). Note that Bash is a requirement of Roddy anyway and I know only of executions in Bash environments. So this change is very unlikely to actually break anything.

Note however, that the SSHExecutionService apparently does not wrap everything into Bash. This may also be a good argument for removing the bash -c wrapping from RoddyToolLib. I'm uncertain. From a conceptual perspective the actual execution and the wrapping in Bash are different things and should be dealt with by different classes (e.g. some kind of BashCommand class that could also do basic syntax checks etc.). Also this may be an argument for actually not doing the wrapping at all. Unfortunately, I am not sure whether actually something would break. So wrapping everything into Bash is the safer solution than removing the wrapping from commands that possibly would break.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What is your opinion?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See the comparison of String.execute() and bash -c in a ProcessBuilder, below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure what the thumb-up means. "Good discussion"? "Leave it like it is"? "Poor guy. Keep up the good mood!" ;-)

@@ -630,7 +630,7 @@ class FileSystemAccessProvider {
boolean setAccessRightsRecursively(File path, String accessStringDirectories, String accessString, String group) {
return ExecutionService.instance.
execute(commandSet.getSetAccessRightsRecursivelyCommand(
path, accessStringDirectories, accessString, group), false)
path, accessStringDirectories, accessString, group))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This will continue to run asynchronously, but with better error reporting.

build.gradle Outdated
@@ -92,7 +92,7 @@ repositories {
}


ext.roddyToolLibVersion = "2.1.1"
ext.roddyToolLibVersion = "2.2.0"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@vinjana vinjana requested review from askask and removed request for askask October 7, 2020 11:14
Copy link
Collaborator

@juleskers juleskers left a comment

Choose a reason for hiding this comment

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

Comments from yesterday, round 1

Still checking the rest together with @jpalbrecht

@@ -1,81 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Consistency: Why delete the .iml?
Comparing to RoddyToolLib, there .idea is gone, but the .iml is kept.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, I did not notice and I am very surprised, because apparently IntelliJ does not need this. The file is gone, but the project module -- now simply imported from the Gradle model -- is present. Not sure yet, what that might mean.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am more or less the only developer left and the system needed to be changed, because IntelliJ apparently does not support the old way with one module including sources and tests. All this is now automatically modelled as submodules and there is no way to change this, because IntelliJ automatically reimports from the Gradle-file.

I suggest we leave this like it is here in the PR.

import de.dkfz.roddy.tools.LoggerWrapper
import groovy.transform.CompileStatic

import java.util.concurrent.Callable
Copy link
Collaborator

Choose a reason for hiding this comment

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

Apparently an unused import, according to IntelliJ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed.


/**
* The local execution service executes commands on the local machine. For this groovy's execute() method is used.
* @author michael
* All commands are executed using Bash as interpreter!
Copy link
Collaborator

Choose a reason for hiding this comment

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

That sounds like a pretty important (breaking?) change.
Should the comment permanently reflect this, e.g. by adding some wording like

prior to version vX.X.X, varying interpreters were used. This was normalised to consistently use bash starting at version


def "execute('echo')"() {
when:
ExecutionResult res = es.execute("echo 'hello'", true)
Copy link
Collaborator

Choose a reason for hiding this comment

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

For consistency compared to the methods below, should there be a waitFor = true in the test name (also for the next)


LocalExecutionService es = ExecutionService.instance

def "execute('echo')"() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

As discussed in meeting: these names can be better 😄

import de.dkfz.roddy.tools.LoggerWrapper
import groovy.transform.CompileStatic

import java.util.concurrent.Callable
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed.


/**
* The local execution service executes commands on the local machine. For this groovy's execute() method is used.
* @author michael
* All commands are executed using Bash as interpreter!
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. I also did not have updated the changelogs. This should be fixed. The choice to do it like this is justified by that for synchronous execution also bash -c is used (via LocalExecutionHelper in RoddyToolLib). Note that Bash is a requirement of Roddy anyway and I know only of executions in Bash environments. So this change is very unlikely to actually break anything.

Note however, that the SSHExecutionService apparently does not wrap everything into Bash. This may also be a good argument for removing the bash -c wrapping from RoddyToolLib. I'm uncertain. From a conceptual perspective the actual execution and the wrapping in Bash are different things and should be dealt with by different classes (e.g. some kind of BashCommand class that could also do basic syntax checks etc.). Also this may be an argument for actually not doing the wrapping at all. Unfortunately, I am not sure whether actually something would break. So wrapping everything into Bash is the safer solution than removing the wrapping from commands that possibly would break.


/**
* The local execution service executes commands on the local machine. For this groovy's execute() method is used.
* @author michael
* All commands are executed using Bash as interpreter!
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@vinjana vinjana requested a review from juleskers October 12, 2020 11:13
…and composition in BashCommandSet.

- Accompanying changes to express optionality of return value in associated methods and handling of these changes.
- Removed unnecessarily complicated Optional usage where simple if/else is better
- Temporarily set RoddyToolLib dependency to test-release number for testing, until upstream changes are accepted
- Fix unpredictable result line order for local synchronous execution tests.

Tests were done with with without --include-build parameters for gradle and `gradle dependencies` to check that the right RoddyToolLib was included, to make sure, RTL and BE 0.0.7 play well together.
Whether it generally is a good idea to produce commands (like `rm -rf $file`), where the file can actually be a glob, one can be sceptical about.

Some futher comments and small Roddy-internal refactorings.
…cCacheFileCommand. Rename a Spock test file.

Otherwise layout and comment changes.
command.execute()
}
return new ExecutionResult(true, 0, [], "")
ProcessBuilder processBuilder = new ProcessBuilder(["bash", "-c", command])
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The old implementation used String.execute(String), which eventually refers to Runtime.exec(String command, String[] end, File dir). This latter method tokenizes the command using the delimiters space, tab, newline, carriage-return, form-feed. Bash, by contrast, uses the delimiters space, tab, newline by default (the $IFS variable), which, however, in this context here boils down to the same, because here only spaces are relevant token-delimiters. This means that in our context, command.execute() and bash -c ... called via ProcessBuilder will do the same.


/**
* The local execution service executes commands on the local machine. For this groovy's execute() method is used.
* @author michael
* All commands are executed using Bash as interpreter!
Copy link
Contributor Author

Choose a reason for hiding this comment

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

See the comparison of String.execute() and bash -c in a ProcessBuilder, below.

}

def "execute('echo \"hello\"; false', waitFor = false)"() {
def "execute synchronously and succeed with empty stdout and non!-ignored stderr"() {
Copy link

@jpalbrecht jpalbrecht Oct 21, 2020

Choose a reason for hiding this comment

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

shouldn't that be a "execute synchronously and fail " (exit code = 1)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry. Maybe a tabular test would have been more appropriate for all these parameter combinations and results.

@juleskers
Copy link
Collaborator

Question: There is a seemingly empty merge-commit in the history.
Is that an accidental artifact?

OK either way if you keep or remove.

README.md Show resolved Hide resolved
@Override
String getCheckChangeOfPermissionsPossibilityCommand(File file, String group) {
File testFile = new File(file, ".roddyPermissionsTestFile")
if (null == group) {

Choose a reason for hiding this comment

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

is Yoda conditions default in Roddy? Seen this at various positions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I try to use these "Yoda conditions" (thanks for the term) to not by chance write group = null. Note that group == null evaluates to true if group is null, but group = null evaluates to false. So, in case of a typo with = rather than ==, not only group would be assigned, but also the other branch would be taken. Just a safety measure.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, I never checked, but apparently Groovy does not accept assignments in conditions. So it is not strictly unnecessary. However, the "added cognitive load" counter-argument on the Wiki page is ridiculously irrelevant in the context of the overall complexity of the code. So I leave it as it is but avoid it in the future. Thanks for pointing out.

Copy link
Collaborator

@juleskers juleskers left a comment

Choose a reason for hiding this comment

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

Pair-reviewed together with @jpalbrecht ;

Good to merge, excluding a few minor remarks, and one incorrect spec-name (copy paste error);
All the other things are basically stylistic things that are also fine as-is.

provider.setDefaultAccessRightsRecursively(new File(analysisToolsServerDir.getAbsolutePath()), context)
boolean success = provider.setDefaultAccessRightsRecursively(new File(analysisToolsServerDir.getAbsolutePath()), context)
if (!success)
logger.warning("Command error occurred and was ignored")
Copy link
Collaborator

Choose a reason for hiding this comment

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

This message is the equivalent of "something went wrong" in it's informativeness.

Alternative suggestion:
"Ignoring command error while setting default access rights on analysisToolsServerDir"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Too true to be true.

- recompile for 2.5.9 release including timestamping
- some code reformating (to be able to read on my narrow notebook screen)
- test class rename to be standard-conform
@vinjana vinjana merged commit e735922 into master Oct 23, 2020
@vinjana vinjana deleted the fix-localexecutionservice-ignores-errors branch October 23, 2020 07:26
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