[SPARK-35907][CORE] Instead of File#mkdirs, Files#createDirectories is expected.#33101
[SPARK-35907][CORE] Instead of File#mkdirs, Files#createDirectories is expected.#33101Shockang wants to merge 14 commits intoapache:masterfrom
Conversation
|
Thank you for making a PR, @Shockang . Could you enable GitHub Action on your Spark fork?
|
|
ok to test |
| dir = null | ||
| } | ||
| } catch { case e: SecurityException => dir = null; } | ||
| // SPARK-35907 Instead of File#mkdirs, Files#createDirectories is expected. |
| } catch { case e: SecurityException => dir = null; } | ||
| // SPARK-35907 Instead of File#mkdirs, Files#createDirectories is expected. | ||
| Files.createDirectories(dir.toPath) | ||
| } catch { case e: Exception => |
There was a problem hiding this comment.
nit. This could cause a regression because catching Exception can hide lots of things. Is there a way to narrow down this?
There was a problem hiding this comment.
nit. This could cause a regression because catching
Exceptioncan hide lots of things. Is there a way to narrow down this?
Ignoring the exception directly will cause users to be confused about the creation failure, and Files#createDirectories will throw more meaningful exception information, but SecurityException is not enough to meet the needs.I will take a more appropriate approach.
There was a problem hiding this comment.
I meant to use a more specific Exception instead of Ignoring the exception. If there is no other way due to the function signature, it's okay. Thanks for the considering it.
There was a problem hiding this comment.
This is a good idea @dongjoon-hyun - Files.createDirectories lists the exceptions which can be thrown, we can restrict to the subset which need to be caught if they are expected to be ignored.
There was a problem hiding this comment.
It looks like this conversation was resolved but the issue still persists. I agree with @mridulm, we should just catch IOException and SecurityException here.
There was a problem hiding this comment.
I intended to write the code like this:
case e @ (_ : FileAlreadyExistsException | _ : IOException | _ : SecurityException) =>
but considering that the above method: createDirectory(File) also uses Exception, it would be better to keep the same here.And the code is not very concise.
There was a problem hiding this comment.
I would argue that createDirectory(File) above is the one doing it wrong and we should correct it there rather than copying it, but will refer to @dongjoon-hyun and @mridulm here.
Btw, FileAlreadyExistsException is an IOException, so you can just do
case e @ (_ : IOException | _ : SecurityException) =>
There was a problem hiding this comment.
I would argue that
createDirectory(File)above is the one doing it wrong and we should correct it there rather than copying it, but will refer to @dongjoon-hyun and @mridulm here.Btw,
FileAlreadyExistsExceptionis anIOException, so you can just docase e @ (_ : IOException | _ : SecurityException) =>
I have a little bit of pursuing code simplicity, thanks to your suggestions, the code now looks much more comfortable.o(^▽^)o
| } catch { | ||
| case e: Exception => | ||
| logError(s"Failed to create directory " + dir, e) | ||
| logError(s"Failed to create directory $dir", e) |
There was a problem hiding this comment.
This is reasonable. But, in general, I discourage adding irrelevant changes like this. To have a minimal and coherent PR content is the best, @Shockang .
There was a problem hiding this comment.
This is reasonable. But, in general, I discourage adding irrelevant changes like this. To have a minimal and coherent PR content is the best, @Shockang .
What you said is right, let me restore it, it makes sense to minimize the modification points.
dongjoon-hyun
left a comment
There was a problem hiding this comment.
Thank you for your first contribution, @Shockang . I left a few comments.
Thanks a lot.I will revise it according to your opinion. |
|
@dongjoon-hyun It has been revised.Could you please verify this patch? |
|
Kubernetes integration test starting |
|
Kubernetes integration test status success |
|
Test build #140364 has finished for PR 33101 at commit
|
| Files.createDirectories(dir.toPath) | ||
| true |
There was a problem hiding this comment.
Hm..this looks like a behavior change to me. Are you sure Files.createDirectories would always not fail silently?
Otherwise, I'd prefer to keep:
if ( !dir.exists() || !dir.isDirectory) {
logError(s"Failed to create directory " + dir)
}
dir.isDirectoryThere was a problem hiding this comment.
In some extreme scenarios, Files.createDirectories would fail silently. In this case, we can submit an issue to the JDK official. ︿( ̄︶ ̄)︿ The purpose of using this method is to provide better exception handling mechanism, but it should not be at the cost of reducing safety, so I take your suggestion.Thanks a lot. (^▽^)
| } catch { case e: SecurityException => dir = null; } | ||
| // SPARK-35907 Instead of File#mkdirs, Files#createDirectories is expected. | ||
| Files.createDirectories(dir.toPath) | ||
| } catch { case e: Exception => |
There was a problem hiding this comment.
It looks like this conversation was resolved but the issue still persists. I agree with @mridulm, we should just catch IOException and SecurityException here.
| if ( !dir.exists() || !dir.isDirectory) { | ||
| logError(s"Failed to create directory " + dir) | ||
| } |
There was a problem hiding this comment.
@Ngone51 I understand you requested this check to be brought back in your previous comment, but it doesn't seem right to me. Based on the Javadoc for createDirectories, the contract is that the directory will be created successfully, or an exception will be thrown. Keeping the exists/isDirectory checks after is redundant. If we're worried about a JDK bug as described here, why do we trust the implementation of File#exists() and File#isDirectory() but not Files.createDirectories()? IMO the whole point of switching from File to the NIO APIs is that they have more sensible semantics and so we don't need to jump through hoops like this.
There was a problem hiding this comment.
Based on the Javadoc for createDirectories, the contract is that the directory will be created successfully, or an exception will be thrown.
I didn't find such a contract there, but only " Unlike the createDirectory method, an exception is not thrown if the directory could not be created because it already exists."...
If we're worried about a JDK bug as described here, why do we trust the implementation of File#exists() and File#isDirectory() but not Files.createDirectories()?
It's not about which method we should trust but which way is safer to go. If there's such a contract as you mentioned, I agree we don't need the check. Otherwise, I think it's safer to follow the original way to avoid suffering from the old issue.
There was a problem hiding this comment.
The word "contract" is not used, but I think that is rarely the case in method descriptions. For example none of the methods on Dataset explicitly state contracts or guarantees, but we are expected to assume that the method will do what it says it does, and indicate an error condition using an exception otherwise. My impression here is that the weird semantics of File#mkdirs() are translating into FUD about the potential shortcomings of Files.createDirectories() and that, if the method were taken without that historical context, we wouldn't be worrying about it.
This all being said, it's only three lines of code extra, so of course it is no big detriment to be on the safe side.
There was a problem hiding this comment.
The word "contract" is not used, but I think that is rarely the case in method descriptions. For example none of the methods on
Datasetexplicitly state contracts or guarantees, but we are expected to assume that the method will do what it says it does, and indicate an error condition using an exception otherwise. My impression here is that the weird semantics ofFile#mkdirs()are translating into FUD about the potential shortcomings ofFiles.createDirectories()and that, if the method were taken without that historical context, we wouldn't be worrying about it.This all being said, it's only three lines of code extra, so of course it is no big detriment to be on the safe side.
I agree with you in terms of safety and minimizing modification points.Do you have any questions with my current code?
There was a problem hiding this comment.
@Shockang Could you leave a comment above the check to give the historical context, e.g.,
"The check was required by File#mkdirs() because it could sporadically fail silently. After switching to Files.createDirectories(), ideally, there should no longer be silent fails. But the check is kept for the safety concern. We can remove the check when we're sure that Files.createDirectories() would never fail silently."
There was a problem hiding this comment.
Do you have any questions with my current code?
No, just wanted to explain my viewpoint. Current code is fine. I agree with @Ngone51 that a comment is helpful to explain the context.
There was a problem hiding this comment.
@Shockang Could you leave a comment above the check to give the historical context, e.g.,
"The check was required by File#mkdirs() because it could sporadically fail silently. After switching to Files.createDirectories(), ideally, there should no longer be silent fails. But the check is kept for the safety concern. We can remove the check when we're sure that Files.createDirectories() would never fail silently."
This comment looks perfect. Thank you very much for your comments.
|
Test build #140450 has finished for PR 33101 at commit
|
|
Kubernetes integration test starting |
|
Kubernetes integration test status success |
|
Kubernetes integration test unable to build dist. exiting with code: 1 |
|
Test build #140456 has finished for PR 33101 at commit
|
| // SPARK-35907 | ||
| // This could throw more meaningful exception information if directory creation failed. |
There was a problem hiding this comment.
I was going to do that, but the program reported an error: File line length exceeds 100 characters. It's about scalastyle.
There was a problem hiding this comment.
I kind of feel the comments aren't necessary? The comment explains why we switched from File#mkdirs() to Files.createDirectories(), but IMO a comment should explain something that is confusing/complicated or unexpected, and if you just come across Files.createDirectories(), there's nothing strange about that.
| // SPARK-35907 | ||
| // This could throw more meaningful exception information if directory creation failed. |
srowen
left a comment
There was a problem hiding this comment.
Looks fine. The tests may be overkill but OK :)
Are there any other instance of .mkdirs() out there that should use the utility method, while we're here?
|
Kubernetes integration test unable to build dist. exiting with code: 1 |
|
Test build #140608 has finished for PR 33101 at commit
|
|
Kubernetes integration test starting |
|
Kubernetes integration test status success |
|
Test build #140611 has finished for PR 33101 at commit
|
Thank you for your attention (。・∀・)ノ゙ In the Spark source code, in addition to the Utils class, there are two user-facing pieces of code and more than 30 test cases using mkdirs. Do you mean I need to replace all these places? |
|
I wouldn't worry about the test cases, but if there are places in the source code that look like they probably should leverage the Utils code to make a dir, in order to get all the same behavior, yeah we could change that too here all at once. Up to your judgment - if in doubt post here and we can figure it out. |
|
Merged to master |
### What changes were proposed in this pull request? Change Worker's workDir from /tmp to Utils.createTempDir in case WorkerSuite ### Why are the changes needed? WorkerSuite fails on MacOS after pr [SPARK-35907](#33101). WorkerSuite tries to create ```/tmp``` dir by ```FIles.createDirectories```, but /tmp is a softlink to /private/tmp on MacOs (both intel and m1), according to suite in [UtilsSuite](https://github.com/apache/spark/blob/master/core/src/test/scala/org/apache/spark/util/UtilsSuite.scala#L525), create directory on existed softlink will cause fail. ### Does this PR introduce _any_ user-facing change? no ### How was this patch tested? pass GA Closes #34420 from toujours33/SPARK-37141. Authored-by: Wang <toujours33> Signed-off-by: Dongjoon Hyun <dongjoon@apache.org>

What changes were proposed in this pull request?
The code of method: createDirectory in class: org.apache.spark.util.Utils is modified.
Why are the changes needed?
To solve the problem of ambiguous exception handling in traditional IO creating directories.
What's more, there shouldn't be an improper comment in Spark's source code.
Does this PR introduce any user-facing change?
Yes
The modified method would be called to create the working directory when Worker starts.
The modified method would be called to create local directories for storing block data when the class: DiskBlockManager instantiates.
The modified method would be called to create a temporary directory inside the given parent directory in several classes.
How was this patch tested?
I have provided test cases as much as possible.
Authored-by: Shockang shockang@aliyun.com