Skip to content

[SPARK-35907][CORE] Instead of File#mkdirs, Files#createDirectories is expected.#33101

Closed
Shockang wants to merge 14 commits intoapache:masterfrom
Shockang:SPARK-35907
Closed

[SPARK-35907][CORE] Instead of File#mkdirs, Files#createDirectories is expected.#33101
Shockang wants to merge 14 commits intoapache:masterfrom
Shockang:SPARK-35907

Conversation

@Shockang
Copy link
Contributor

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

@github-actions github-actions bot added the CORE label Jun 26, 2021
@dongjoon-hyun
Copy link
Member

Thank you for making a PR, @Shockang . Could you enable GitHub Action on your Spark fork?

Screen Shot 2021-06-26 at 10 00 40 AM

@dongjoon-hyun
Copy link
Member

ok to test

dir = null
}
} catch { case e: SecurityException => dir = null; }
// SPARK-35907 Instead of File#mkdirs, Files#createDirectories is expected.
Copy link
Member

Choose a reason for hiding this comment

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

ditto.

} catch { case e: SecurityException => dir = null; }
// SPARK-35907 Instead of File#mkdirs, Files#createDirectories is expected.
Files.createDirectories(dir.toPath)
} catch { case e: Exception =>
Copy link
Member

Choose a reason for hiding this comment

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

nit. This could cause a regression because catching Exception can hide lots of things. Is there a way to narrow down this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

nit. This could cause a regression because catching Exception can 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.

Copy link
Member

@dongjoon-hyun dongjoon-hyun Jun 28, 2021

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like this conversation was resolved but the issue still persists. I agree with @mridulm, we should just catch IOException and SecurityException here.

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

Copy link
Contributor

Choose a reason for hiding this comment

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

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

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

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

@dongjoon-hyun dongjoon-hyun Jun 27, 2021

Choose a reason for hiding this comment

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

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 .

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

Copy link
Member

@dongjoon-hyun dongjoon-hyun left a comment

Choose a reason for hiding this comment

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

Thank you for your first contribution, @Shockang . I left a few comments.

@Shockang
Copy link
Contributor Author

Thank you for your first contribution, @Shockang . I left a few comments.

Thanks a lot.I will revise it according to your opinion.

@Shockang
Copy link
Contributor Author

Shockang commented Jun 29, 2021

@dongjoon-hyun It has been revised.Could you please verify this patch?

@SparkQA
Copy link

SparkQA commented Jun 29, 2021

Kubernetes integration test starting
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/44895/

@SparkQA
Copy link

SparkQA commented Jun 29, 2021

Kubernetes integration test status success
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/44895/

@SparkQA
Copy link

SparkQA commented Jun 29, 2021

Test build #140364 has finished for PR 33101 at commit 435d125.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

Comment on lines 290 to 291
Files.createDirectories(dir.toPath)
true
Copy link
Member

Choose a reason for hiding this comment

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

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

Copy link
Contributor Author

@Shockang Shockang Jun 30, 2021

Choose a reason for hiding this comment

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

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 =>
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like this conversation was resolved but the issue still persists. I agree with @mridulm, we should just catch IOException and SecurityException here.

Comment on lines 292 to 294
if ( !dir.exists() || !dir.isDirectory) {
logError(s"Failed to create directory " + dir)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

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

Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

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.

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

I agree with you in terms of safety and minimizing modification points.Do you have any questions with my current code?

Copy link
Member

Choose a reason for hiding this comment

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

@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."

Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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

@SparkQA
Copy link

SparkQA commented Jun 30, 2021

Test build #140450 has finished for PR 33101 at commit b367ea6.

  • This patch fails to build.
  • This patch merges cleanly.
  • This patch adds no public classes.

@SparkQA
Copy link

SparkQA commented Jun 30, 2021

Kubernetes integration test starting
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/44964/

@SparkQA
Copy link

SparkQA commented Jun 30, 2021

Kubernetes integration test status success
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/44964/

@SparkQA
Copy link

SparkQA commented Jun 30, 2021

Kubernetes integration test unable to build dist.

exiting with code: 1
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/44969/

@SparkQA
Copy link

SparkQA commented Jun 30, 2021

Test build #140456 has finished for PR 33101 at commit 983bb36.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

Comment on lines 288 to 289
// SPARK-35907
// This could throw more meaningful exception information if directory creation failed.
Copy link
Member

Choose a reason for hiding this comment

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

nit: "SPARK-35907: This could..."

Copy link
Contributor Author

@Shockang Shockang Jul 2, 2021

Choose a reason for hiding this comment

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

I was going to do that, but the program reported an error: File line length exceeds 100 characters. It's about scalastyle.

Copy link
Contributor

@xkrogen xkrogen Jul 2, 2021

Choose a reason for hiding this comment

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

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.

Comment on lines 319 to 320
// SPARK-35907
// This could throw more meaningful exception information if directory creation failed.
Copy link
Member

Choose a reason for hiding this comment

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

nit: "SPARK-35907: This could..."

Copy link
Member

@srowen srowen left a comment

Choose a reason for hiding this comment

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

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?

@SparkQA
Copy link

SparkQA commented Jul 3, 2021

Kubernetes integration test unable to build dist.

exiting with code: 1
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/45120/

@SparkQA
Copy link

SparkQA commented Jul 3, 2021

Test build #140608 has finished for PR 33101 at commit a958a5e.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@SparkQA
Copy link

SparkQA commented Jul 3, 2021

Kubernetes integration test starting
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/45124/

@SparkQA
Copy link

SparkQA commented Jul 3, 2021

Kubernetes integration test status success
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/45124/

@SparkQA
Copy link

SparkQA commented Jul 3, 2021

Test build #140611 has finished for PR 33101 at commit 6d123ba.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@Shockang
Copy link
Contributor Author

Shockang commented Jul 5, 2021

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?

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?

@srowen
Copy link
Member

srowen commented Jul 5, 2021

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.

@srowen
Copy link
Member

srowen commented Jul 7, 2021

Merged to master

@srowen srowen closed this in 55373b1 Jul 7, 2021
dongjoon-hyun pushed a commit that referenced this pull request Oct 28, 2021
### 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants