[JENKINS-42934] Avoid using new FileInputStream / new FileOutputStream#2816
[JENKINS-42934] Avoid using new FileInputStream / new FileOutputStream#2816daniel-beck merged 6 commits intojenkinsci:masterfrom
Conversation
|
Also @jenkinsci/code-reviewers |
|
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. |
|
It looks like the same change is needed in git client plugin for its use of new FileInputStream. Nice catch |
jglick
left a comment
There was a problem hiding this 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.)
|
Some of the test failures seem legitimate |
|
@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) |
…r the file will not be created
oleg-nenashev
left a comment
There was a problem hiding this 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)
| 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(); |
There was a problem hiding this comment.
🐛 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.
There was a problem hiding this comment.
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 ;-) )
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
Hmm, why? I would understand the InvalidPathException: https://docs.oracle.com/javase/7/docs/api/java/io/File.html#toPath()
There was a problem hiding this comment.
Other calls do not also catch it
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
… replaced by NoSuchFileException by JVM shenanigans
oleg-nenashev
left a comment
There was a problem hiding this comment.
🐝 since the issue in the test suite is resolved (in quite different way, but OK).
|
🐝 |
|
@reviewbybees done |
|
@jenkinsci/code-reviewers PING |
|
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 { |
There was a problem hiding this comment.
Is this safe to do? It may result in unhandled checked exceptions, right?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
Assuming newOutputStream throws the latter, isn't the former obsolete now?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
Shouldn't there be tons of unused imports removed in this PR?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
IntelliJ highlights (low-lights?) unused imports and makes them trivial to remove.
Still, just wondered, not required IMO.
|
@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()); |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
@stephenc Why not use Files.createFile() with an exists check?
svanoort
left a comment
There was a problem hiding this 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.
…tream behavior after jenkinsci/jenkins#2816.
See JENKINS-42934 and JDK-8080225
@reviewbybees