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

[JENKINS-42934] Avoid using new FileInputStream / new FileOutputStream #2816

Merged
merged 6 commits into from Mar 30, 2017

Conversation

@stephenc
Copy link
Member

stephenc commented Mar 20, 2017

@stephenc
Copy link
Member Author

stephenc commented Mar 20, 2017

@reviewbybees
Copy link

reviewbybees commented Mar 20, 2017

This pull request originates from a CloudBees employee. At CloudBees, we require that all pull requests be reviewed by other CloudBees employees before we seek to have the change accepted. If you want to learn more about our process please see this explanation.

@MarkEWaite
Copy link
Contributor

MarkEWaite commented Mar 20, 2017

It looks like the same change is needed in git client plugin for its use of new FileInputStream. Nice catch

@jglick
jglick approved these changes Mar 20, 2017
Copy link
Member

jglick left a comment

Sounds good to me but should there be a FindBugs rule for this? Or should we let the JDK team decide whether these classes should be deprecated unless and until a proper fix is made? (In practice they seem very reluctant to deprecate anything, even in cases where there is a demonstrable danger.)

@stephenc
Copy link
Member Author

stephenc commented Mar 20, 2017

Some of the test failures seem legitimate

@stephenc
Copy link
Member Author

stephenc commented Mar 20, 2017

@jglick a findbugs rule to catch any use of FileInputStream / FileOutputStream would probably be a good idea for a follow-up... as would corresponding changes in all plugins... but let's get this change tested and even better if we can run it through the ATH (not sure how to do that as the ATH has never worked on my machine)

stephenc added 2 commits Mar 20, 2017
…r the file will not be created
Copy link
Member

oleg-nenashev left a comment

The most of the changes look good to me, but the approach in UtilTest#lockFileForDeletion() may likely cause instability in tests according to my experience with Java FileChannel API. Hence 🐛 though it is a weak one (test code)

@@ -490,9 +494,13 @@ private void lockFileForDeletion(File f) throws IOException, InterruptedExceptio
// On unix, can't use "chattr +u" because ext fs ignores it.
// On Windows, we can't delete files that are open for reading, so we use that.
assert Functions.isWindows();
final InputStream s = new FileInputStream(f);
final FileChannel channel = FileChannel.open(f.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE);
final FileLock lock = channel.lock();

This comment has been minimized.

Copy link
@oleg-nenashev

oleg-nenashev Mar 21, 2017

Member

🐛 Such approach may cause the OverlappingFileLockException if the lock is being already held by the JVM. It will cause an unhadled and random Runtime exception here. The best pattern is a loop with tryLock(), but surprisingly it also has to handle this exception.

This comment has been minimized.

Copy link
@stephenc

stephenc Mar 21, 2017

Author Member

How is this worse than the code that was there before?

The point of code review is not to get the reviewed code perfect, but to ensure that the code quality is an increasing function. i.e. dQ/dC < 0 is grounds to put a bug but dQ/dC == 0 is not (though dQ/dC > 0 is preferred obviously)

If we keep the attitude that dQ/dC == ∞ then we will never make any changes

(where Q is quality and C is changes to the code)

With this code, if run on a windows filesystem that does not support locking we will get a test Error rather than a test Failure... which IMHO is actually the correct result... so to put the point further, I would argue that this is actually the correct behaviour from test code (although my intent had been like for like so this is more of an unintended virtuous side-effect ;-) )

This comment has been minimized.

Copy link
@oleg-nenashev

oleg-nenashev Mar 21, 2017

Member

Likely it was a response to #2816 (comment), or maybe I do not get the response

OverlappingFileLockException will be happening on the system, which does support file locking. And, ridiculously, these exceptions happen on the success path (JVMs synchronize between each other, but JVM does not sync locks within itself. likely a bad design, but as designed).

@@ -490,9 +494,13 @@ private void lockFileForDeletion(File f) throws IOException, InterruptedExceptio
// On unix, can't use "chattr +u" because ext fs ignores it.
// On Windows, we can't delete files that are open for reading, so we use that.
assert Functions.isWindows();
final InputStream s = new FileInputStream(f);
final FileChannel channel = FileChannel.open(f.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE);

This comment has been minimized.

Copy link
@oleg-nenashev

oleg-nenashev Mar 21, 2017

Member

Though it is correct change for file locking API, it follows the presumption that All windows FileSystems support locking. And it is not correct. If the locking is not supported, IIRC the lock() call throws the IOException, which goes outside the method. Maybe it would be better to check if locking is supported in assert/assume

current = new FileOutputStream(out,false);
} catch (FileNotFoundException e) {
current = Files.newOutputStream(out.toPath(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
} catch (FileNotFoundException | NoSuchFileException e) {

This comment has been minimized.

Copy link
@oleg-nenashev

oleg-nenashev Mar 21, 2017

Member

Hmm, why? I would understand the InvalidPathException: https://docs.oracle.com/javase/7/docs/api/java/io/File.html#toPath()

This comment has been minimized.

Copy link
@oleg-nenashev

oleg-nenashev Mar 21, 2017

Member

Other calls do not also catch it

This comment has been minimized.

Copy link
@stephenc

stephenc Mar 21, 2017

Author Member

It's from Files.newOutputStream IIUC

Checked exception thrown when an attempt is made to access a file that does not exist.

So basically it seems that the JVM had NIH with itself

This comment has been minimized.

Copy link
@oleg-nenashev

oleg-nenashev Mar 21, 2017

Member

Uh oh, you are right. http://stackoverflow.com/questions/14263725/java-files-write-nosuchfileexception. Does it make sense to catch it in other usages then?

stephenc added 2 commits Mar 21, 2017
… replaced by NoSuchFileException by JVM shenanigans
Copy link
Member

oleg-nenashev left a comment

🐝 since the issue in the test suite is resolved (in quite different way, but OK).

@rsandell
Copy link
Member

rsandell commented Mar 22, 2017

🐝

@oleg-nenashev
Copy link
Member

oleg-nenashev commented Mar 22, 2017

@stephenc
Copy link
Member Author

stephenc commented Mar 22, 2017

@reviewbybees
Copy link

reviewbybees commented Mar 23, 2017

This pull request has completed our internal processes and we now respectfully request the maintainers of this repository to consider our proposal contained within this pull request for merging.

@@ -78,22 +76,22 @@ public CompressedFile(File file) {
/**
* Gets the OutputStream to write to the file.
*/
public OutputStream write() throws FileNotFoundException {
public OutputStream write() throws IOException {

This comment has been minimized.

Copy link
@daniel-beck

daniel-beck Mar 23, 2017

Member

Is this safe to do? It may result in unhandled checked exceptions, right?

This comment has been minimized.

Copy link
@stephenc

stephenc Mar 29, 2017

Author Member

Well every case I could find was catching the IOException anyway

} catch (FileNotFoundException e) {
current = Files.newOutputStream(out.toPath(), StandardOpenOption.CREATE,
appendOnNextOpen ? StandardOpenOption.APPEND : StandardOpenOption.TRUNCATE_EXISTING);
} catch (FileNotFoundException | NoSuchFileException e) {

This comment has been minimized.

Copy link
@daniel-beck

daniel-beck Mar 23, 2017

Member

Assuming newOutputStream throws the latter, isn't the former obsolete now?

This comment has been minimized.

Copy link
@stephenc

stephenc Mar 29, 2017

Author Member

The javadoc for newOutputStream does not mandate which of these should be thrown so safer to catch both

@@ -137,16 +139,9 @@ public static void safeTransform(@Nonnull Source source, @Nonnull Result out) th
throw new IllegalArgumentException(String.format("File %s does not exist or is not a 'normal' file.", file.getAbsolutePath()));
}

FileInputStream fileInputStream = new FileInputStream(file);

This comment has been minimized.

Copy link
@daniel-beck

daniel-beck Mar 23, 2017

Member

Shouldn't there be tons of unused imports removed in this PR?

This comment has been minimized.

Copy link
@stephenc

stephenc Mar 29, 2017

Author Member

I was trying to keep the diff minimal... if you want I can optimize imports on the modified files, but that was re-sorting them for me with my current IDE and generating a large diff

This comment has been minimized.

Copy link
@daniel-beck

daniel-beck Mar 29, 2017

Member

IntelliJ highlights (low-lights?) unused imports and makes them trivial to remove.

Still, just wondered, not required IMO.

@oleg-nenashev
Copy link
Member

oleg-nenashev commented Mar 29, 2017

@stephenc There is still some unaddressed comments

@@ -174,11 +173,8 @@ public static int remotePost(String[] args) throws Exception {
con.setFixedLengthStreamingMode((int)tmpFile.length());
con.connect();
// send the data
FileInputStream in = new FileInputStream(tmpFile);
try {
try (InputStream in = Files.newInputStream(tmpFile.toPath())) {
Util.copyStream(in,con.getOutputStream());

This comment has been minimized.

Copy link
@svanoort

svanoort Mar 29, 2017

Member

We're no longer closing quietly here...

@@ -1134,7 +1134,7 @@ public static String xmlEscape(@Nonnull String text) {
* Creates an empty file.
*/
public static void touch(@Nonnull File file) throws IOException {
new FileOutputStream(file).close();
Files.newOutputStream(file.toPath()).close();

This comment has been minimized.

Copy link
@svanoort

svanoort Mar 29, 2017

Member

@stephenc Why not use Files.createFile() with an exists check?

Copy link
Member

svanoort left a comment

Looks reasonable to me -- there may be opportunities for additional improvements in some of the IO methods, but better to make a small change now than wait for perfection.

@daniel-beck daniel-beck merged commit bde09f7 into jenkinsci:master Mar 30, 2017
2 checks passed
2 checks passed
Jenkins This pull request looks good
Details
continuous-integration/jenkins/pr-head This commit looks good
Details
mdkf added a commit to mdkf/change-assembly-version-plugin that referenced this pull request Sep 8, 2017
jglick added a commit to jglick/compress-artifacts-plugin that referenced this pull request Mar 5, 2018
…tream behavior after jenkinsci/jenkins#2816.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

8 participants
You can’t perform that action at this time.