-
Notifications
You must be signed in to change notification settings - Fork 114
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
Check preconditions in FileSystemResourceManager.write #103 #393
Conversation
Test Results 591 files + 18 591 suites +18 59m 10s ⏱️ - 10m 31s Results for commit e40518f. ± Comparison against base commit 05d6dea. This pull request removes 1 and adds 2 tests. Note that renamed tests count towards both.
♻️ This comment has been updated with latest results. |
...lipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java
Outdated
Show resolved
Hide resolved
*/ | ||
public void write(IFile target, InputStream content, IFileInfo fileInfo, int updateFlags, boolean append, IProgressMonitor monitor) throws CoreException { | ||
SubMonitor subMonitor = SubMonitor.convert(monitor, 4); | ||
try { | ||
assertExists(target); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you consider throwing a CoreException to maintain the contract of the API, instead of an IllegalStateException?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jukzi Maybe this question is rather directed to you, since you worked on https://git.eclipse.org/r/c/platform/eclipse.platform.resources/+/176136
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I have to admit that I am not sure whether I understood the contract correctly. I expected the existance of IFile target
to be a precondition for executing the method, which is why an IllegalStateException
would make sense to me if called with a non-existing target
. I expected the condition regarding throwing a CoreException
to refer to the static information in the given fileInfo
or in the target
, given that the latter is present.
But you are right that in certain situations the method will throw a different exception now, so I am completely open to throwing a CoreException
instead. However, there is another test case that expects an IllegalStateException
to be thrown when passing a non-existing target (see #103 (comment)).
Either way, as soon as we have agreed we may also improve the contract to clarify its meaning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding an checked Exception instead of an RuntimeException would break API. However CoreException is already documented to be thrown ... so it would be OK to use it for already documented cases.
However i wonder if it even is according to the javadoc if any excpetion is thrown for an non existing file:
"If the force flag is false we only write the file if it does not exist" does "exists" here mean in workspace or in filesystem?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either way, as soon as we have agreed we may also improve the contract to clarify its meaning.
Changing the contract is bad even if it is an improvement in some cases - there may be code that relies on the contract.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jukzi would be great to have your feedback on which kind of exception you would expect to be thrown here (in case you have an opinion on that). thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The javadoc clearly specifies CoreException should be thrown if file is missing. However as far i remember the root for the IllegalStateException was that platform was not really sure if the file exists or not. A proper fix would be to avoid to get into such state - just don't know how exactly that is reached. Performancewise It might regression to add more costly checks in this method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing the contract is bad even if it is an improvement in some cases - there may be code that relies on the contract.
True. What I actually meant was that we should adapt the Javadoc so that it properly represents the existing behavior of the method, since the specification is currently not very precise.
The javadoc clearly specifies CoreException should be thrown if file is missing.
That is one possible interpretation, but I would say that it "clearly specifies" this. Throwing a CoreException
is specified for the case that the force flag is false and the other mentioned conditions are not fulfilled. The Javadoc does not make a statement about the exception thrown in the case that the target is not existing. Actually, it defines this as a precondition, which is why I find an IllegalStateException
appropriate here. In addition, the current code throws an IllegalStateException
at several points where the resource info is null.
However as far i remember the root for the IllegalStateException was that platform was not really sure if the file exists or not. A proper fix would be to avoid to get into such state - just don't know how exactly that is reached.
The reason I saw so far is that the file is concurrently removed and a concurrent workspace refresh is performed, such that the file is not represented in the workspace tree anymore. So maybe a proper/better solution would be to acquire a workspace lock during the write operation or to even wrap it into a workspace operation. But I fear that will at least introduce a performance issue and may, in the worst case, even lead to deadlocks.
41932f2
to
049421a
Compare
049421a
to
2a6fe05
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i still don't see my concerns answered
...lipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java
Outdated
Show resolved
Hide resolved
*/ | ||
public void write(IFile target, InputStream content, IFileInfo fileInfo, int updateFlags, boolean append, IProgressMonitor monitor) throws CoreException { | ||
SubMonitor subMonitor = SubMonitor.convert(monitor, 4); | ||
try { | ||
assertExists(target); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding an checked Exception instead of an RuntimeException would break API. However CoreException is already documented to be thrown ... so it would be OK to use it for already documented cases.
However i wonder if it even is according to the javadoc if any excpetion is thrown for an non existing file:
"If the force flag is false we only write the file if it does not exist" does "exists" here mean in workspace or in filesystem?
*/ | ||
public void write(IFile target, InputStream content, IFileInfo fileInfo, int updateFlags, boolean append, IProgressMonitor monitor) throws CoreException { | ||
SubMonitor subMonitor = SubMonitor.convert(monitor, 4); | ||
try { | ||
assertExists(target); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either way, as soon as we have agreed we may also improve the contract to clarify its meaning.
Changing the contract is bad even if it is an improvement in some cases - there may be code that relies on the contract.
*/ | ||
public void write(IFile target, InputStream content, IFileInfo fileInfo, int updateFlags, boolean append, IProgressMonitor monitor) throws CoreException { | ||
SubMonitor subMonitor = SubMonitor.convert(monitor, 4); | ||
try { | ||
assertExists(target); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The javadoc clearly specifies CoreException should be thrown if file is missing. However as far i remember the root for the IllegalStateException was that platform was not really sure if the file exists or not. A proper fix would be to avoid to get into such state - just don't know how exactly that is reached. Performancewise It might regression to add more costly checks in this method.
...lipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java
Outdated
Show resolved
Hide resolved
5043b54
to
bf87b99
Compare
Sometimes things are much simpler than they first appear 🙂
The reason is quite simple: this (and all the other exceptions due to I invested quite some time to produce concurrent updates of the workspace that leads to the resource info becoming So if I understand correctly, the fix for all the different places in |
bf87b99
to
494b31a
Compare
4a7a2b1
to
06ad2ba
Compare
The build fails - beside that have you tested, that checking the existence conditions twice does not lead to a performance degradation? I don't know a solution but it would feel better to have some sort of atomic operation by synchronization instead of double check - which may depend on timing. |
I forget to post the documentation of my recent update of this PR. Sorry for that, you can find it at the end of this comment.
I do not expect any performance degradation, because the existence check is only a With respect to the build failures: they seem to be unrelated test failures, but I will have another eye on them once we agree on the change. For documentation, I have made the following changes in my recent update of this PR:
It is still up to the caller of the method to ensure that the |
@jukzi May I ask you to have another look at this PR together with my explanations in #393 (comment)? |
An assumption is not enough. Please verify that. this code is very often used and a bottle neck on windows during build. |
Thanks for your reply! Actually my comment was not assumption but an assessment. But I see that this is a critical code part, so I will elaborate more based on a static analysis as well as on a test-based evaluation. Code Analysis
Test-Based EvaluationI did some tests based on different scenarios. I use worst case properties, precisely the force flag being set, so that in the original implementation the resource info is retrieved only once, where the new implementation does it twice. The most important question is how much overhead the additional execution of that method produces. Scenario 1A workspace with 100.000 files directly placed below a project to which a string of 8 characters is written. Scenario 2A workspace with 30.000 files placed in different folders of depth 20 within a project to which a string of 8 characters is written. This scenario is taken since Remarks
ResultsIn summary, both from a code analysis perspective as well as from a test-based evaluation perspective there is no noticable performance impact of the change. The only relevant thread to validity of these results I see is the selection of scenarios. In case there are other scenarios that dramatically increase the execution time of @jukzi Do you see further necessities for verification or does this fit to your expectation? |
Those measurements where taken on which OS?
Jörg Kubitz
… Am 17.11.2023 um 12:17 schrieb Heiko Klare ***@***.***>:
An assumption is not enough. Please verify that. this code is very often used and a bottle neck on windows during build.
Thanks for your reply! Actually my comment was not assumption but an assessment. But I see that this is a critical code part, so I will elaborate more based on a static analysis as well as on a test-based evaluation.
Code Analysis
The only change affecting performance in the commit is the getResourceInfo() call that is moved from within a conditional block to outside, thus now may being executed more often. But this only happens if the target is not local or the force flag is set.
getResourceInfo() is called again later in the method, so an additional call of that method will only create a "linear" overhead w.r.t. to the time consumed by getResourceInfo() calls.
getResourceInfo() does not require a significant amount of time, so the linear overhead w.r.t. to that method call is not relevant (see samples below).
Test-Based Evaluation
I did some tests based on different scenarios. I use worst case properties, precisely the force flag being set, so that in the original implementation the resource info is retrieved only once, where the new implementation does it twice. The most important question is how much overhead the additional execution of that method produces.
I wrote different amounts of files with different workspace structures and sampled the write() calls.
Scenario 1
A workspace with 100.000 files directly placed below a project to which a string of 8 characters is written.
Original Implementation:
New implementation:
Scenario 2
A workspace with 30.000 files placed in different folders of depth 20 within a project to which a string of 8 characters is written. This scenario is taken since getResourceInfo() needs to traverse the workspace tree, so deeper hierarchies may produce more execution time.
Original implementation:
New implementation:
Remarks
There is quite some variation in the execution times (sometime getResourceInfo() is not listed as its below threshold, sometimes it's even at 400ms).
Most important is the ratio of getResourceInfo() execution time compared to overall write() execution time and the overall write() execution time itself, which do significantly change with the modifications in this PR.
Results
In summary, both from a code analysis perspective as well as from a test-based evaluation perspective there is no noticable performance impact of the change. The only relevant thread to validity of these results I see is the selection of scenarios. In case there are other scenarios that dramatically increase the execution time of getResourceInfo() compared to everything else in the write() method, there would be a performance impact. I do not see a way how that could happen, but if I miss something, of course let me know.
@jukzi Do you see further necessities for verification or does this fit to your expectation?
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you were mentioned.
|
Measurements were taken on Windows 11. Since only the file-system access is OS-dependent while |
06ad2ba
to
f324630
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
then i am OK with this change in M1
Thanks @jukzi for all the feedback and the thorough review! So I guess we can remove your request for changes? I will of course respin and investigate the checks before merging for M1. |
…rm#103 The FileSystemResourceManager.write() method fails when the target file has been deleted before the write method has been called. The method defines the existence of the file in the workspace as a precondition but does never check that, which makes the method fail when first accessing information that is not available after the deletion, in particular, its ResourceInfo. This is why the method failed when accessing the ResourceInfo. The FileSystemResourceManagerTest.testWriteFile() randomly fails because of this behavior. A concurrently running workspace refresh removes the file right before the write() method is called. - Make write() check its precondition, i.e., the existence of the target file to deterministically fail with an IllegalStateException - Make testWriteFile() wait for the refresh to be finished before executing write() to have deterministic test behavior - Add additional test case validating proper failure of write() when target file has been deleted before Fixes eclipse-platform#103
Document the current behavior of the FileSystemResourceManager.write() method in terms of parameter meanings and conditions for exceptions to be thrown. Contributes to eclipse-platform#103
f324630
to
e40518f
Compare
lets try... please keep an eye on next i-builds to see if there are any regressions |
The
FileSystemResourceManager.write()
method fails when thetarget
file has been deleted before the write method has been called. The method defines the existence of the file in the workspace as a precondition but does never check that, which makes the method fail when first accessing information that is not available after the deletion, in particular, its ResourceInfo. This is why the method failed when accessing the
ResourceInfo`.The
FileSystemResourceManagerTest.testWriteFile()
randomly fails because of this behavior. A concurrently running workspace refresh removes the file right before thewrite()
method is called.write()
check its precondition, i.e., the existence of the target file to deterministically fail with anIllegalStateException
testWriteFile()
wait for the refresh to be finished before executingwrite()
to have deterministic test behaviorwrite()
when target file has been deleted beforewrite()
method documentation to reflect the current behavior.Fixes #103