Skip to content

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

Merged
daniel-beck merged 6 commits intojenkinsci:masterfrom
stephenc:jenkins-42934
Mar 30, 2017
Merged

[JENKINS-42934] Avoid using new FileInputStream / new FileOutputStream#2816
daniel-beck merged 6 commits intojenkinsci:masterfrom
stephenc:jenkins-42934

Conversation

@stephenc
Copy link
Copy Markdown
Member

See JENKINS-42934 and JDK-8080225

@reviewbybees

@stephenc
Copy link
Copy Markdown
Member Author

Also @jenkinsci/code-reviewers

@oleg-nenashev oleg-nenashev added the needs-more-reviews Complex change, which would benefit from more eyes label Mar 20, 2017
@ghost
Copy link
Copy Markdown

ghost 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
Copy Markdown
Contributor

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

Copy link
Copy Markdown
Member

@jglick jglick left a comment

Choose a reason for hiding this comment

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

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
Copy Markdown
Member Author

Some of the test failures seem legitimate

@stephenc
Copy link
Copy Markdown
Member Author

@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)

Copy link
Copy Markdown
Member

@oleg-nenashev oleg-nenashev left a comment

Choose a reason for hiding this comment

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

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)

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();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🐛 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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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 ;-) )

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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).

// 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);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Other calls do not also catch it

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

@oleg-nenashev oleg-nenashev left a comment

Choose a reason for hiding this comment

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

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

@rsandell
Copy link
Copy Markdown
Member

🐝

@oleg-nenashev
Copy link
Copy Markdown
Member

@reviewbybees done

@stephenc
Copy link
Copy Markdown
Member Author

@jenkinsci/code-reviewers PING

@ghost
Copy link
Copy Markdown

ghost 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.

* Gets the OutputStream to write to the file.
*/
public OutputStream write() throws FileNotFoundException {
public OutputStream write() throws IOException {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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

throw new IllegalArgumentException(String.format("File %s does not exist or is not a 'normal' file.", file.getAbsolutePath()));
}

FileInputStream fileInputStream = new FileInputStream(file);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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

Still, just wondered, not required IMO.

@oleg-nenashev
Copy link
Copy Markdown
Member

@stephenc There is still some unaddressed comments

FileInputStream in = new FileInputStream(tmpFile);
try {
try (InputStream in = Files.newInputStream(tmpFile.toPath())) {
Util.copyStream(in,con.getOutputStream());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We're no longer closing quietly here...

*/
public static void touch(@Nonnull File file) throws IOException {
new FileOutputStream(file).close();
Files.newOutputStream(file.toPath()).close();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member

@svanoort svanoort left a comment

Choose a reason for hiding this comment

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

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
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-more-reviews Complex change, which would benefit from more eyes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants